"""------------------------------------------------------------
导入的模块
-------------------------------------------------------------"""
# 导入标准 GUI（图形用户界面）库 tkinter 并简称为 tk
import tkinter as tk
# 导入数学库，提供各种数学函数
import math
# 导入多维数组对象和各种数学函数的库 numpy 并简称为 np
import numpy as np
# 从 tkinter 导入 messagebox，用于弹出消息框
from tkinter import messagebox
# 导入数据处理和分析的库 pandas 并简称为 pd
import pandas as pd
# 导入绘图库 matplotlib.pyplot 并简称为 plt
import matplotlib.pyplot as plt
# 从 matplotlib.backends.backend_tkagg 导入 FigureCanvasTkAgg，用于将 matplotlib 绘制的图表嵌入到 tkinter 窗口
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
# 从 matplotlib.figure 导入 Figure，用于创建绘图的 Figure 对象
from matplotlib.figure import Figure
# 导入用于地理编码和地理距离计算的库 geodesic
from geopy.distance import geodesic
# 导入用于创建 KML 文件的库 simplekml
import simplekml
# 从 tkinter 导入 filedialog，用于文件选择对话框
from tkinter import filedialog
# 导入日期和时间处理库 datetime 并简称为 dt
import datetime as dt
# 导入用于计算循环冗余校验（CRC）值的库 crcmod
import crcmod
# 导入用于二进制和 ASCII 码之间转换的库 binascii
import binascii
# 导入用于处理 CSV（逗号分隔值）文件读写操作的库 csv
import csv
# 导入用于读写 Excel 文件的库 openpyxl
import openpyxl
# 从 openpyxl 导入 load_workbook，用于加载 Excel 工作簿
from openpyxl import load_workbook
# 从 datetime 导入 datetime，用于处理日期和时间
from datetime import datetime,timezone
# 导入 Python 的正则表达式库 re
import re
# 导入用于读取和写入配置文件的库 configparser
import configparser
# 导入提供与操作系统进行交互功能的库 os
import os
# 导入用于实现多线程编程的库 threading
import threading
# 从 tkinter 导入 ttk，用于创建更美观的 GUI 组件
from tkinter import ttk
# 从 datetime 导入 datetime 和 timedelta，用于处理日期和时间的加减操作
from datetime import datetime, timedelta
# 导入用于自动化浏览器操作的库 selenium 的 webdriver
from selenium import webdriver
# 从 selenium.webdriver.chrome.service 导入 Service，用于设置 Chrome 浏览器驱动服务
from selenium.webdriver.chrome.service import Service
# 从 selenium.webdriver.common.by 导入 By，用于定位网页元素
from selenium.webdriver.common.by import By
# 导入时间处理库 time
import time
# 再次导入 os 库，不过这里重复导入没有必要，可移除
import os
# 从 time 导入 strftime，用于格式化时间
from time import strftime
# 从 keplergl 导入 KeplerGl
from keplergl import KeplerGl
# 导入用于创建交互式地图的库 folium
import folium
# 导入用于打开网页的库 webbrowser
import webbrowser
# 导入用于发送 HTTP 请求的库 requests
import requests
# 导入用于处理 JSON 数据的库 json
import json
# 再次从 datetime 导入 datetime 和 timedelta，可移除重复导入
from datetime import datetime, timedelta
# 导入用于获取系统平台信息的库 platform
import platform
# 导入用于创建具有主题的 tkinter 窗口的库 ThemedTk
from ttkthemes import ThemedTk
# 从 tkinter 导入 IntVar，用于处理整数类型的变量
from tkinter import IntVar
from folium.plugins import AntPath
from folium.plugins import MousePosition
from folium.plugins import MeasureControl, Draw
"""------------------------------------------------------------
兼容MACOS

-------------------------------------------------------------"""
# 检查当前操作系统是否为 macOS（Darwin 是 macOS 的内核名称）
if platform.system() == 'Darwin':
    import matplotlib
    matplotlib.use('TkAgg')

"""------------------------------------------------------------
主窗口设置

-------------------------------------------------------------"""
# 创建一个 Tkinter 主窗口对象
window = tk.Tk()
window.title("Queclink Tool")
# 禁止用户调整窗口的大小，第一个参数为宽度方向，第二个参数为高度方向
window.resizable(False, False)
# 窗口居中操作
width = 1000
height = 700
# 获取当前屏幕的宽度和高度窗口居中
screenwidth = window.winfo_screenwidth()
screenheight = window.winfo_screenheight()
size_geo = '%dx%d+%d+%d' % (width, height, (screenwidth - width) // 2, (screenheight - height) // 2)
window.geometry(size_geo)

# 创建一个主菜单对象，该菜单关联到前面创建的主窗口
main_menu = tk.Menu(window)

'''------------------------------------------------------------
主窗口时钟

-------------------------------------------------------------'''
# 定义函数，用于更新时间显示
def time1():
    # 获取当前时间并格式化
    string = strftime('%H:%M:%S %p')
    time_label.config(text=string)
    time_label.after(1000, time1)

# 创建显示时间的标签，设置字体样式
time_label = tk.Label(window, font=('calibri', 80, 'bold'))
time_label.place(relx=0.25, rely=0.3)

# 调用 time 函数，启动时间更新
time1()

'''------------------------------------------------------------
清空主窗口说明

-------------------------------------------------------------'''
#销毁除菜单外的子窗口
def delete_widget():
    for control in window.winfo_children():
        if not isinstance(control, tk.Menu):
            control.destroy()

'''------------------------------------------------------------
鼠标悬浮说明

-------------------------------------------------------------'''
class ToolTip:
    def __init__(self, widget):
        # 初始化 ToolTip 类，接收一个 widget 作为参数
        # widget 是要添加提示框的目标控件
        self.widget = widget
        # 用于存储提示框的窗口对象，初始化为 None
        self.tip_window = None

    def showtip(self, text):
        # 显示提示框，接收提示文本作为参数
        # 如果提示框已存在或文本为空，则不做处理
        if self.tip_window or not text:
            return
        # 获取提示框的位置
        x, y = self._get_tip_position()
        # 创建顶层窗口作为提示框
        tip_window = tk.Toplevel(self.widget)
        # 去除窗口边框
        tip_window.wm_overrideredirect(1)
        # 设置提示框的位置
        tip_window.wm_geometry(f"+{x}+{y}")
        # 创建标签显示提示文本
        tk.Label(tip_window, text=text, justify=tk.LEFT,
                 background="#ffffe0", relief=tk.SOLID, borderwidth=1,
                 font=('宋体', "10", "normal")).pack(ipadx=1)
        # 保存提示框的窗口对象
        self.tip_window = tip_window

    def hidetip(self):
        # 隐藏提示框
        if self.tip_window:
            # 销毁提示框窗口
            self.tip_window.destroy()
            # 将提示框窗口对象置为 None
            self.tip_window = None

    def _get_tip_position(self):
        # 获取提示框的位置
        # 获取 widget 的位置信息
        x, y, cx, cy = self.widget.bbox("insert")
        # 计算提示框的 x 坐标
        # 加上 widget 的根 x 坐标和偏移量 27
        # 计算提示框的 y 坐标
        # 加上 widget 的根 y 坐标、控件高度和偏移量 27
        return (x + self.widget.winfo_rootx() + 27,
                y + cy + self.widget.winfo_rooty() + 27)


def create_tooltip(widget, text):
    # 创建提示框的辅助函数，接收 widget 和提示文本作为参数
    # 创建 ToolTip 类的实例
    tool_tip = ToolTip(widget)
    # 绑定鼠标进入事件，当鼠标进入 widget 时显示提示框
    widget.bind('<Enter>', lambda _: tool_tip.showtip(text))
    # 绑定鼠标离开事件，当鼠标离开 widget 时隐藏提示框
    widget.bind('<Leave>', lambda _: tool_tip.hidetip())


'''------------------------------------------------------------
转换欧拉角菜单功能设置

-------------------------------------------------------------'''
def rotation_euler():
    delete_widget()

    x_forward_entry = tk.Entry(window, width=10)
    x_forward_entry.place(relx=0.12, rely=0.03)
    x_forward_label = tk.Label(window, text='x_forward：', font=('微软雅黑', 10))
    x_forward_label.place(relx=0.02, rely=0.03)

    y_forward_entry = tk.Entry(window, width=10)
    y_forward_entry.place(relx=0.32, rely=0.03)
    y_forward_label = tk.Label(window, text='Y_Forward：', font=('微软雅黑', 10))
    y_forward_label.place(relx=0.22, rely=0.03)

    z_forward_entry = tk.Entry(window, width=10)
    z_forward_entry.place(relx=0.52, rely=0.03)
    z_forward_label = tk.Label(window, text='Z_Forward：', font=('微软雅黑', 10))
    z_forward_label.place(relx=0.42, rely=0.03)

    x_side_entry = tk.Entry(window, width=10)
    x_side_entry.place(relx=0.12, rely=0.09)
    x_side_label = tk.Label(window, text='X_Side：', font=('微软雅黑', 10))
    x_side_label.place(relx=0.02, rely=0.09)

    y_side_entry = tk.Entry(window, width=10)
    y_side_entry.place(relx=0.32, rely=0.09)
    y_side_label = tk.Label(window, text='Y_Side：', font=('微软雅黑', 10))
    y_side_label.place(relx=0.22, rely=0.09)

    z_side_entry = tk.Entry(window, width=10)
    z_side_entry.place(relx=0.52, rely=0.09)
    z_side_label = tk.Label(window, text='Z_Side：', font=('微软雅黑', 10))
    z_side_label.place(relx=0.42, rely=0.09)

    x_vertical_entry = tk.Entry(window, width=10)
    x_vertical_entry.place(relx=0.12, rely=0.15)
    x_vertical_label = tk.Label(window, text='X_Vertical：', font=('微软雅黑', 10))
    x_vertical_label.place(relx=0.02, rely=0.15)

    y_vertical_entry = tk.Entry(window, width=10)
    y_vertical_entry.place(relx=0.32, rely=0.15)
    y_vertical_label = tk.Label(window, text='Y_Vertical：', font=('微软雅黑', 10))
    y_vertical_label.place(relx=0.22, rely=0.15)

    z_vertical_entry = tk.Entry(window, width=10)
    z_vertical_entry.place(relx=0.52, rely=0.15)
    z_vertical_label = tk.Label(window, text='Z_Vertical：', font=('微软雅黑', 10))
    z_vertical_label.place(relx=0.42, rely=0.15)

    pitch_angle_entry = tk.Entry(window, width=15)
    pitch_angle_entry.place(relx=0.1, rely=0.31)
    pitch_angle_label = tk.Label(window, text='Pitch_angle：', font=('微软雅黑', 10))
    pitch_angle_label.place(relx=0.01, rely=0.31)

    roll_angle_entry = tk.Entry(window, width=15)
    roll_angle_entry.place(relx=0.3, rely=0.31)
    roll_angle_label = tk.Label(window, text='Roll_angle：', font=('微软雅黑', 10))
    roll_angle_label.place(relx=0.21, rely=0.31)

    yaw_angle_entry = tk.Entry(window, width=15)
    yaw_angle_entry.place(relx=0.5, rely=0.31)
    yaw_angle_label = tk.Label(window, text='Yaw_angle：', font=('微软雅黑', 10))
    yaw_angle_label.place(relx=0.4, rely=0.31)


#旋转矩阵转换欧拉角函数
    def convert_euler():
        try:
            x_forward = float(x_forward_entry.get())
            y_forward = float(y_forward_entry.get())
            z_forward = float(z_forward_entry.get())
            x_side = float(x_side_entry.get())
            y_side = float(y_side_entry.get())
            x_forward = float(x_forward_entry.get())
            y_forward = float(y_forward_entry.get())
            z_forward = float(z_forward_entry.get())
            x_side = float(x_side_entry.get())
            y_side = float(y_side_entry.get())
            z_side = float(z_side_entry.get())
            x_vertical = float(x_vertical_entry.get())
            y_vertical = float(y_vertical_entry.get())
            z_vertical = float(z_vertical_entry.get())
        except Exception as e:
            messagebox.showerror('ERROR', '请输入正确的校准因子！')

        R = np.array([[x_forward, y_forward, z_forward], [x_side, y_side, z_side], [x_vertical, y_vertical, z_vertical]])
        assert R.shape == (3,3)
        yaw = np.arctan2(R[1,0], R[0,0])
        pitch = np.arcsin(-R[2,0])
        roll = np.arctan2(R[2,1], R[2,2])
        roll = np.rad2deg(roll)
        pitch = np.rad2deg(pitch)
        yaw = np.rad2deg(yaw)

        return roll, pitch, yaw

#转换后的欧拉角显示函数
    def show_euler():
        pitch_angle_entry.delete(0, 'end')
        roll_angle_entry.delete(0, 'end')
        yaw_angle_entry.delete(0, 'end')
        euler_angles = convert_euler()
        pitch_angle_entry.insert(0, euler_angles[1])
        roll_angle_entry.insert(0, euler_angles[0])
        yaw_angle_entry.insert(0, euler_angles[2])


    convert_euler_button = tk.Button(window, text = '转换欧拉角', command = show_euler)
    convert_euler_button.place(x = 250, y = 160)
    create_tooltip(convert_euler_button, "将校准因子转换成pitch、roll、yaw角")

#main_menu.add_command (label="旋转矩阵转换",command = Rotation_Euler)


'''------------------------------------------------------------
转换旋转矩阵菜单功能设置

-------------------------------------------------------------'''
def euler_rotation():
    delete_widget()

    x_forward_entry = tk.Entry(window, width=10)
    x_forward_entry.place(relx=0.12, rely=0.2)
    x_forward_label = tk.Label(window, text='X_Forward：', font=('微软雅黑', 10))
    x_forward_label.place(relx=0.02, rely=0.2)

    y_forward_entry = tk.Entry(window, width=10)
    y_forward_entry.place(relx=0.32, rely=0.2)
    y_forward_label = tk.Label(window, text='Y_Forward：', font=('微软雅黑', 10))
    y_forward_label.place(relx=0.22, rely=0.2)

    z_forward_entry = tk.Entry(window, width=10)
    z_forward_entry.place(relx=0.52, rely=0.2)
    z_forward_label = tk.Label(window, text='Z_Forward：', font=('微软雅黑', 10))
    z_forward_label.place(relx=0.42, rely=0.2)

    x_side_entry = tk.Entry(window, width=10)
    x_side_entry.place(relx=0.12, rely=0.26)
    x_side_label = tk.Label(window, text='X_Side：', font=('微软雅黑', 10))
    x_side_label.place(relx=0.02, rely=0.26)

    y_side_entry = tk.Entry(window, width=10)
    y_side_entry.place(relx=0.32, rely=0.26)
    y_side_label = tk.Label(window, text='Y_Side：', font=('微软雅黑', 10))
    y_side_label.place(relx=0.22, rely=0.26)

    z_side_entry = tk.Entry(window, width=10)
    z_side_entry.place(relx=0.52, rely=0.26)
    z_side_label = tk.Label(window, text='Z_Side：', font=('微软雅黑', 10))
    z_side_label.place(relx=0.42, rely=0.26)

    x_vertical_entry = tk.Entry(window, width=10)
    x_vertical_entry.place(relx=0.12, rely=0.32)
    x_vertical_label = tk.Label(window, text='X_Vertical：', font=('微软雅黑', 10))
    x_vertical_label.place(relx=0.02, rely=0.32)

    y_vertical_entry = tk.Entry(window, width=10)
    y_vertical_entry.place(relx=0.32, rely=0.32)
    y_vertical_label = tk.Label(window, text='Y_Vertical：', font=('微软雅黑', 10))
    y_vertical_label.place(relx=0.22, rely=0.32)

    z_vertical_entry = tk.Entry(window, width=10)
    z_vertical_entry.place(relx=0.52, rely=0.32)
    z_vertical_label = tk.Label(window, text='Z_Vertical：', font=('微软雅黑', 10))
    z_vertical_label.place(relx=0.42, rely=0.32)

    pitch_angle_entry = tk.Entry(window, width=15)
    pitch_angle_entry.place(relx=0.12, rely=0.03)
    pitch_angle_label = tk.Label(window, text='Pitch_angle：', font=('微软雅黑', 10))
    pitch_angle_label.place(relx=0.01, rely=0.03)

    roll_angle_entry = tk.Entry(window, width=15)
    roll_angle_entry.place(relx=0.32, rely=0.03)
    roll_angle_label = tk.Label(window, text='Roll_angle：', font=('微软雅黑', 10))
    roll_angle_label.place(relx=0.21, rely=0.03)

    yaw_angle_entry = tk.Entry(window, width=15)
    yaw_angle_entry.place(relx=0.52, rely=0.03)
    yaw_angle_label = tk.Label(window, text='Yaw_angle：', font=('微软雅黑', 10))
    yaw_angle_label.place(relx=0.4, rely=0.03)


#欧拉角转换旋转矩阵函数
    def convert_rotation():
        try:
            pitch_angle = float(pitch_angle_entry.get())
            roll_angle = float(roll_angle_entry.get())
            yaw_angle = float(yaw_angle_entry.get())
        except Exception as e:
            messagebox.showerror('ERROR', '请输入正确的欧拉角！')

        r = np.array([pitch_angle, roll_angle,yaw_angle])
        roll = np.deg2rad(r[1])
        pitch = np.deg2rad(r[0])
        yaw = np.deg2rad(r[2])

        r_x = np.array([
        [1,0,0],
        [0,math.cos(roll),-math.sin(roll)],
        [0,math.sin(roll),math.cos(roll) ]])
        r_y = np.array([
        [math.cos(pitch),0,math.sin(pitch)],
        [0,1,0],
        [-math.sin(pitch),0,math.cos(pitch)]])
        r_z = np.array([
        [math.cos(yaw),-math.sin(yaw),0],
        [math.sin(yaw),math.cos(yaw),0],
        [0,0,1]])

        r = np.dot(r_z, np.dot( r_y, r_x ))

        return r

#转换后的旋转矩阵显示函数
    def show_rotation():
        x_forward_entry.delete(0, 'end')
        y_forward_entry.delete(0, 'end')
        z_forward_entry.delete(0, 'end')
        x_side_entry.delete(0, 'end')
        y_side_entry.delete(0, 'end')
        z_side_entry.delete(0, 'end')
        x_vertical_entry.delete(0, 'end')
        y_vertical_entry.delete(0, 'end')
        z_vertical_entry.delete(0, 'end')
        rotation = convert_rotation()
        x_forward_entry.insert(0, rotation[0][0])
        y_forward_entry.insert(0, rotation[0][1])
        z_forward_entry.insert(0, rotation[0][2])
        x_side_entry.insert(0, rotation[1][0])
        y_side_entry.insert(0, rotation[1][1])
        z_side_entry.insert(0, rotation[1][2])
        x_vertical_entry.insert(0, rotation[2][0])
        y_vertical_entry.insert(0, rotation[2][1])
        z_vertical_entry.insert(0, rotation[2][2])


    convert_euler_button = tk.Button(window, text = '转换欧拉角', command = show_rotation)
    convert_euler_button.place(x = 250, y = 80)
    create_tooltip(convert_euler_button, "将pitch、roll、yaw角转换成校准因子")

#main_menu.add_command (label="欧拉角转换",command = Euler_Rotation)


'''------------------------------------------------------------
距离计算菜单功能设置



-------------------------------------------------------------'''
def distance_show():
    delete_widget()
    frame_point = tk.LabelFrame(window, height = 50, width = 1000, text="单点计算", relief = 'groove')
    frame_point.place(relx = 0, rely = 0)

    Longitude1_Entry = tk.Entry(frame_point, width = 10)
    Longitude1_Entry.place(relx = 0.08, rely = 0.0)
    Longitude1_Label = tk.Label(frame_point, text = 'Longitude1:', font = ('微软雅黑',8,))
    Longitude1_Label.place(relx = 0, rely = 0, width=80, height=20)

    Latitude1_Entry = tk.Entry(frame_point, width = 10)
    Latitude1_Entry.place(relx = 0.25, rely = 0.0)
    Latitude1_Label = tk.Label(frame_point, text = 'Latitude1:', font = ('微软雅黑',8,))
    Latitude1_Label.place(relx = 0.17, rely = 0, width=80, height=20)

    Longitude2_Entry = tk.Entry(frame_point, width = 10)
    Longitude2_Entry.place(relx = 0.42, rely = 0.0)
    Longitude2_Label = tk.Label(frame_point, text = 'Longitude2:', font = ('微软雅黑',8,))
    Longitude2_Label.place(relx = 0.34, rely = 0, width=80, height=20)

    Latitude2_Entry = tk.Entry(frame_point, width = 10)
    Latitude2_Entry.place(relx = 0.59, rely = 0.0)
    Latitude2_Label = tk.Label(frame_point, text = 'Latitude2:', font = ('微软雅黑',8,))
    Latitude2_Label.place(relx = 0.51, rely = 0, width=80, height=20)



    Neighbor_point = tk.LabelFrame(window, height = 320, width = 1000, text="邻点计算", relief = 'groove')
    Neighbor_point.place(x = 0, y = 50)
    Road_point = tk.LabelFrame(window, height = 320, width = 1000, text="路线计算", relief = 'groove')
    Road_point.place(x = 0, y = 380)

    def cal_one():
        try:
            lon1 = float(Longitude1_Entry.get())
            lat1 = float(Latitude1_Entry.get())
            lon2 = float(Longitude2_Entry.get())
            lat2 = float(Latitude2_Entry.get())
            location1 = (lat1, lon1)
            location2 = (lat2, lon2)
            distance = geodesic(location1, location2).meters
            distance = round(distance, 2)
            cal_Entry.insert(0, distance)
        except Exception as e:
            messagebox.showerror('ERROR', '请输入正确的经纬度信息！')

    cal_dis_point_Button = tk.Button(frame_point, text = '计算', command =  cal_one)
    cal_dis_point_Button.place(relx = 0.69, rely = 0, width=80, height=20)
    create_tooltip(cal_dis_point_Button, "计算两个点之间的距离")

    cal_Entry = tk.Entry(frame_point, width = 10)
    cal_Entry.place(relx = 0.86, rely = 0.0)
    cal_Label = tk.Label(frame_point, text = '输出(米):', font = ('微软雅黑',8,))
    cal_Label.place(relx = 0.78, rely = 0, width=80, height=20)

    def cal_dis_neib():
        distance_values = []
        fri_cal_path = filedialog.askopenfilename(filetypes = [('txt', '*.txt')])
        config = configparser.ConfigParser()
        config.read('configure.ini')
        gnss_lon_num = int(config.get('Section1', 'GNSS_longitude'))
        gnss_lat_num = int(config.get('Section1', 'GNSS_latitude'))
        with open(fri_cal_path, "r", encoding='utf-8') as file:
            lines = file.readlines()

        first_lon = -1
        first_lat = -1

        fri_lines = sort_pick(lines)
        for line in fri_lines:
            parts = line.split(',')
            cur_lon = float(parts[gnss_lon_num])
            cur_lat = float(parts[gnss_lat_num])
            if first_lon == -1 or first_lat == -1 :
                first_lon = cur_lon
                first_lat = cur_lat
            else:
                dist = vectorized_geodesic(first_lat, first_lon, cur_lat, cur_lon)
                distance_values.append(dist)
                first_lon = cur_lon
                first_lat = cur_lat

        fig = Figure(figsize=(9.5, 3), dpi=100)
        ax = fig.add_subplot(111)
        ax.plot(distance_values)
        ax.set_ylabel('D-value')
        ax.set_xlabel('Time')
        ax.set_title("Calculate neighbor distance")
        canvas = FigureCanvasTkAgg(fig, master = window)
        canvas.draw()
        canvas.get_tk_widget().place(relx = 0.06, rely = 0.09)
        fig.savefig('N-value.png')
        messagebox.showinfo("执行结果", "计算成功!")


    cal_dis_neib_Button = tk.Button(Neighbor_point, text = '计算', command =  cal_dis_neib)
    cal_dis_neib_Button.place(relx = 0.01, rely = 0.2, width=40, height=200)
    create_tooltip(cal_dis_neib_Button, "计算GTFRI报文相邻两个点之间的距离")

    def cal_dis_road():
        df = pd.read_excel('Distance diff.xlsx', header = None)
        lon1 = df.iloc[:, 0].values
        lat1 = df.iloc[:, 1].values
        lon2 = df.iloc[:, 2].values
        lat2 = df.iloc[:, 3].values

        def vectorized_geodesic(lat1, lon1, lat2, lon2):
            def geodist(lat1, lon1, lat2, lon2):
                return geodesic((lat1, lon1), (lat2, lon2)).meters
            vfunc = np.vectorize(geodist)
            return vfunc(lat1, lon1, lat2, lon2)

        distance = vectorized_geodesic(lat1, lon1, lat2, lon2)
        df.loc[:, 4] = distance
        df.to_excel('Distance diff.xlsx', index = False, header = False, startrow = 0, startcol = 0)

        col_data = df.iloc[:, 4]
        fig = Figure(figsize=(9.5, 3), dpi=100)
        ax = fig.add_subplot(111)
        ax.plot(col_data)
        ax.set_ylabel('D-value')
        ax.set_xlabel('Time')
        ax.set_title("Calculate Road")
        canvas = FigureCanvasTkAgg(fig, master = window)
        canvas.draw()
        canvas.get_tk_widget().place(relx = 0.06, rely = 0.56)
        fig.savefig('D-value.png')
        messagebox.showinfo("执行结果", "计算成功!")

    cal_dis_neib_Button = tk.Button(Road_point, text = '计算', command =  cal_dis_road)
    cal_dis_neib_Button.place(relx = 0.01, rely = 0.2, width=40, height=200)
    create_tooltip(cal_dis_neib_Button, "计算两条路线对应UTC时间两个点之间的距离")


#main_menu.add_command (label = "距离计算", command = Distance_show)

'''------------------------------------------------------------
KML转换菜单功能设置



-------------------------------------------------------------'''
config = configparser.ConfigParser()
config.read('tool_collection_configure.ini')
imei_index = int(config.get('sort', 'imei'))

def small_tool_collection():
    delete_widget()
    global crc_initial_entry, crc_result_entry

    function_choice = tk.LabelFrame(window, height=70, width=460, text="KML工具", relief='groove')
    function_choice.place(relx=0.01, rely=0.01)
    crc_choice = tk.LabelFrame(window, height=100, width=490, text="CRC校验", relief='groove')
    crc_choice.place(relx=0.50, rely=0.01)
    time_choice = tk.LabelFrame(window, height=100, width=490, text="Time计算", relief='groove')
    time_choice.place(relx=0.50, rely=0.18)
    all_message_choice = tk.LabelFrame(window, height=280, width=460, text="报文处理", relief='groove')
    all_message_choice.place(relx=0.01, rely=0.11)

    #提取GTFRI，并转换为KML
    kml_button = tk.Button(function_choice, text = 'KML转换', pady = 1, padx = 1, command = convert_kml, width = 12)
    kml_button.place(relx = 0.02, rely = 0.05)
    create_tooltip(kml_button, "说明：\n  1.选择报文文件txt格式\n  2.打开tool_collection_configure.ini，填写GNSS_longitude和GNSS_latitude位置\n  3.位置标准：以+RESP为起点，从0开始数longitude和latitude位置\n输出：\n1.输出文件格式KML_20xx-xx-xx_xx-xx-xx.kml")

    #提取RMC并转换成KML
    catch_rmc_Button = tk.Button(function_choice, text = '提取RMC', pady = 1, padx = 1, command = catch_rmc, width = 12)
    catch_rmc_Button.place(relx = 0.35, rely = 0.05)
    create_tooltip(catch_rmc_Button, "功能：将NMEA语句中GNRMC提取出并将经纬度转换，保存为RMC_file.txt和RMC.kml")

    #计算CRC校验
    crc_initial_entry = tk.Entry(crc_choice, width=65, justify='center')
    crc_initial_entry.place(relx=0.02, rely=0.04)
    crc_result_entry = tk.Entry(crc_choice, width=20, justify='center')
    crc_result_entry.place(relx=0.4, rely=0.5)
    cal_crc_button = tk.Button(crc_choice, text='计算', command = cal_crc)
    cal_crc_button.place(relx=0.12, rely=0.5, width=60, height=30)
    create_tooltip(crc_initial_entry, "说明：\n1.填写从<Message Type>到<Count Number>")
    create_tooltip(crc_result_entry, "说明：计算结果")

    #计算时间差
    global  time1_entry, time2_entry, time3_entry, time4_entry
    label1 = tk.Label(time_choice, text='Time1:', justify='center', width=5, height=1, )
    label1.place(relx=0.01, rely=0.1)
    label2 = tk.Label(time_choice, text='Time2:', justify='center', width=5, height=1, )
    label2.place(relx=0.01, rely=0.5)
    time1_entry = tk.Entry(time_choice, width=14)
    time1_entry.place(relx=0.12, rely=0.1)
    create_tooltip(time1_entry, "填写时间格式20250115112136")
    time2_entry = tk.Entry(time_choice, width=14)
    time2_entry.place(relx=0.12, rely=0.5)
    create_tooltip(time2_entry, "填写时间格式20250115112136")
    time3_entry = tk.Entry(time_choice, width=14)
    time3_entry.place(relx=0.75, rely=0.1)
    create_tooltip(time3_entry, "计算单位s")
    time4_entry = tk.Entry(time_choice, width=14)
    time4_entry.place(relx=0.75, rely=0.5)
    create_tooltip(time4_entry, "计算单位h")
    cal_button = tk.Button(time_choice, text='计算', pady=1, padx=1, width=10, command=cal_time)
    cal_button.place(relx=0.45, rely=0.25)
    create_tooltip(cal_button, "计算时间差")

    #报文处理
    global check_ascii, check_hex, all_message_entry, imei_entry
    check_ascii = IntVar()
    check_hex = IntVar()
    label_protocol = tk.Label(all_message_choice, text='IMEI:', justify='center', width=6, height=1, )
    label_protocol.place(relx=0.01, rely=0.02)
    imei_entry = tk.Entry(all_message_choice, width=18)
    imei_entry.place(relx=0.15, rely=0.02)
    create_tooltip(imei_entry, "过滤其它IMEI报文，不填则处理所有报文")
    check_ascii = tk.Checkbutton(all_message_choice, text="ASCII", variable=check_ascii, onvalue=1, offvalue=0)
    check_ascii.place(relx=0.55, rely=0.02)
    create_tooltip(check_ascii, "Ascii报文处理")
    check_hex = tk.Checkbutton(all_message_choice, text="HEX", variable=check_hex, onvalue=1, offvalue=0)
    check_hex.place(relx=0.75, rely=0.02)
    create_tooltip(check_hex, "Hex报文处理")
    all_message_entry = tk.Entry(all_message_choice, width=50)
    all_message_entry.place(relx=0.01, rely=0.22)
    all_message_choose_but = tk.Button(all_message_choice, text='报文选取', pady=1, padx=1, command = all_message_choose, width = 10)
    all_message_choose_but.place(relx=0.8, rely=0.2)
    sort_but = tk.Button(all_message_choice, text='报文排序', pady=1, padx=1, command=sort_message, width=10)
    sort_but.place(relx=0.01, rely=0.40)


def sort_message():
    imei_lines = []
    with open(filepath_all_message, "r", encoding='utf-8') as file:
        lines = file.readlines()
    if not imei_entry.get():
        new_lines = sort(lines)
    else:
        for line in lines:
            parts = line.split(',')
            if imei_entry.get() == parts[imei_index].strip():
                imei_lines.append(line)
        new_lines = sort(imei_lines)

    with open('sort_messages.txt', "w", encoding='utf-8') as file:
        file.writelines(new_lines)

    messagebox.showinfo("结果","文件排序完成，请查看sort_messages.txt！")
    return new_lines

def all_message_choose():
    global filepath_all_message
    all_message_entry.delete(0, tk.END)
    filepath_all_message = filedialog.askopenfilename(filetypes = [('txt', '*.txt')])
    all_message_entry.insert(0, filepath_all_message)

def convert_kml():
    filepath = filedialog.askopenfilename(filetypes = [('txt', '*.txt')])
    kml = simplekml.Kml()
    now = datetime.now()
    formatted_now = now.strftime("%Y-%m-%d_%H-%M-%S")
    file_name = f"KML_{formatted_now}.kml"

    config = configparser.ConfigParser()
    config.read('tool_collection_configure.ini')
    gnss_lon_pos = int(config.get('KML', 'GNSS_longitude'))
    gnss_lat_pos = int(config.get('KML', 'GNSS_latitude'))

    with open(filepath, "r", encoding='utf-8') as file:
        lines = file.readlines()
    fri_lines = sort_pick(lines)

    if gnss_lon_pos != -1 or gnss_lat_pos != -1:
        try:
            for i, line in enumerate(fri_lines, start=1):
                parts = line.split(',')
                gnss_lon = float(parts[gnss_lon_pos])
                gnss_lat = float(parts[gnss_lat_pos])
                # 添加点到 KML 文件
                pnt = kml.newpoint(name=str(i), coords=[(gnss_lon, gnss_lat)])
            kml.save(file_name)
            messagebox.showinfo("执行结果", "KML转换成功!")
        except Exception as e:
            messagebox.showerror("错误", f'kml转换失败: {str(e)}')
    else:
        messagebox.showerror("错误", "请检查tool_collection_configure.ini配置的经纬度位置是否与报文对应！")


def catch_rmc():
    filepath = filedialog.askopenfilename(filetypes = [('txt', '*.txt')])
    results = []
    rmc_lines = []
    kml = simplekml.Kml()
    try:
        with open(filepath, "r", encoding='utf-8') as file:
            rmc_lines = file.readlines()

        for nmea_line in rmc_lines:
            if "GNRMC" in nmea_line:
                start_index = nmea_line.find("GNRMC")
                nmea_line = nmea_line[start_index: ]
                parts = nmea_line.split(',')
                latitude = convert_to_degree(parts[3].strip())  # 纬度
                longitude = convert_to_degree(parts[5].strip())
                parts[3] = str(latitude)
                parts[5] = str(longitude)
                new_nmea_line = ','.join(parts)
                parts = new_nmea_line.split(',')
                gnss_lon = float(parts[5])
                gnss_lat = float(parts[3])
                pnt = kml.newpoint(coords=[(gnss_lon, gnss_lat)])
                results.append(new_nmea_line)
        kml.save("RMC.kml")

        with open('RMC_file.txt', "w", encoding='utf-8') as file:
            file.writelines(results)
        messagebox.showinfo("执行结果", "提取RMC成功，请查看RMC_file.txt!")
    except Exception as e:
        messagebox.showerror("错误", f'提取RMC失败: {str(e)}')

def cal_crc():
    crc_result_entry.delete(0, tk.END)
    str = crc_initial_entry.get()
    crc16_func = crcmod.mkCrcFun(0x11021, rev=False, initCrc=0xFFFF, xorOut=0x0000)
    input = str.replace(' ', '').replace('0x', '').replace(',', '')
    crc_result_entry.insert(0, hex(crc16_func(binascii.unhexlify(input))).upper().replace('0X', ''))


'''------------------------------------------------------------
计时器功能设置



-------------------------------------------------------------'''
def button_Motion_click():
    try:
        now_time = dt.datetime.now().strftime('%F' + ' ' + '%T')
        txt = '点火动：' + now_time + '\n'
        label = tk.Label(window, text = txt,width = 45, height = 8, relief = 'sunken', font = ('微软雅黑', 10))
        label.place(x = 0, y = 0)
        file = open(filepath, 'a')
        file.write(txt)
        file.close()
    except Exception as e:
            messagebox.showerror('ERROR', '请先选择文件路径！！')

def button_Motionless_click():
    try:
        now_time = dt.datetime.now().strftime('%F' + ' ' + '%T')
        txt = '点火静：' + now_time + '\n'
        label = tk.Label(window, text = txt,width = 45, height = 8, relief = 'sunken', font = ('微软雅黑', 10))
        label.place(x = 0, y = 0)
        file = open(filepath, 'a')
        file.write(txt)
        file.close()
    except Exception as e:
            messagebox.showerror('ERROR', '请先选择文件路径！！')


def button_Ign_off_click():
    try:
        now_time = dt.datetime.now().strftime('%F' + ' ' + '%T')
        txt = '熄火：' + now_time + '\n'
        label = tk.Label(window, text = txt,width = 45, height = 8, relief = 'sunken', font = ('微软雅黑', 10))
        label.place(x = 0, y = 0)
        file = open(filepath, 'a')
        file.write(txt)
        file.close()
    except Exception as e:
            messagebox.showerror('ERROR', '请先选择文件路径！！')

def button_ACC_click():
    try:
        now_time = dt.datetime.now().strftime('%F' + ' ' + '%T')
        txt = '急加：' + now_time + '\n'
        label = tk.Label(window, text = txt,width = 45, height = 8, relief = 'sunken', font = ('微软雅黑', 10))
        label.place(x = 0, y = 0)
        file = open(filepath, 'a')
        file.write(txt)
        file.close()
    except Exception as e:
            messagebox.showerror('ERROR', '请先选择文件路径！！')

def button_BRA_click():
    try:
        now_time = dt.datetime.now().strftime('%F' + ' ' + '%T')
        txt = '急减：' + now_time + '\n'
        label = tk.Label(window, text = txt,width = 45, height = 8, relief = 'sunken', font = ('微软雅黑', 10))
        label.place(x = 0, y = 0)
        file = open(filepath, 'a')
        file.write(txt)
        file.close()
    except Exception as e:
            messagebox.showerror('ERROR', '请先选择文件路径！！')

def button_TURN_click():
    try:
        now_time = dt.datetime.now().strftime('%F' + ' ' + '%T')
        txt = '转弯：' + now_time + '\n'
        label = tk.Label(window, text = txt,width = 45, height = 8, relief = 'sunken', font = ('微软雅黑', 10))
        label.place(x = 0, y = 0)
        file = open(filepath, 'a')
        file.write(txt)
        file.close()
    except Exception as e:
            messagebox.showerror('ERROR', '请先选择文件路径！！')

def button_ACC_TURN_click():
    try:
        now_time = dt.datetime.now().strftime('%F' + ' ' + '%T')
        txt = '急加转弯：' + now_time + '\n'
        label = tk.Label(window, text = txt,width = 45, height = 8, relief = 'sunken', font = ('微软雅黑', 10))
        label.place(x = 0, y = 0)
        file = open(filepath, 'a')
        file.write(txt)
        file.close()
    except Exception as e:
            messagebox.showerror('ERROR', '请先选择文件路径！！')

def button_BRA_TURN_click():
    try:
        now_time = dt.datetime.now().strftime('%F' + ' ' + '%T')
        txt = '急减转弯：' + now_time + '\n'
        label = tk.Label(window, text = txt,width = 45, height = 8, relief = 'sunken', font = ('微软雅黑', 10))
        label.place(x = 0, y = 0)
        file = open(filepath, 'a')
        file.write(txt)
        file.close()
    except Exception as e:
            messagebox.showerror('ERROR', '请先选择文件路径！！')

def Timer():
    delete_widget()
    global filepath
    filepath = filedialog.asksaveasfilename(filetypes = [('TXT', '*.txt')])
    button_Motion = tk.Button(window, text = '点火动', width = 15, height = 2, command = button_Motion_click).place(x = 400, y = 10)
    button_Motionless = tk.Button(window, text = '点火静', width = 15, height = 2, command = button_Motionless_click).place(x = 400, y = 70)
    button_Ign_off = tk.Button(window, text = '熄火', width = 15, height = 2, command = button_Ign_off_click).place(x = 400, y = 130)
    button_ACC = tk.Button(window, text = '急加', width = 10, height = 2, command = button_ACC_click).place(x = 0, y = 220)
    button_BRA = tk.Button(window, text = '急减', width = 10, height = 2, command = button_BRA_click).place(x = 120, y = 220)
    button_TURN = tk.Button(window, text = '转弯', width = 10, height = 2, command = button_TURN_click).place(x = 220, y = 220)
    button_ACC_TURN = tk.Button(window, text = '急加转弯', width = 10, height = 2, command = button_ACC_TURN_click).place(x = 320, y = 220)
    button_BRA_TURN = tk.Button(window, text = '急减转弯', width = 10, height = 2, command = button_BRA_TURN_click).place(x = 420, y = 220)
    label1 = tk.Label(window, text = '', width = 45, height = 8, relief = 'sunken', font = ('微软雅黑', 10)).place(x = 0, y = 0)
    label2 = tk.Label(window, text = filepath, width = 45, height = 1, relief = 'sunken', font = ('微软雅黑', 10)).place(x = 0, y = 180)



#main_menu.add_command (label = "计时器", command = Timer)

'''------------------------------------------------------------
绘图菜单功能设置



-------------------------------------------------------------'''
def one_draw():
    one_data = []
    plt.rcParams['font.family'] = 'SimHei'
    config = configparser.ConfigParser()
    config.read('configure.ini')
    Parameter = int(config.get('Section1', 'Parameter'))
    Parameter_Name = config.get('Section1', 'Parameter_topic')
    with open(txt_data, "r", encoding='utf - 8') as file:
        lines = file.readlines()
    sort_lines = sorted(lines, key=get_hex_value)
    FRI_lines = sort_pick(sort_lines)
    for line in FRI_lines:
        parts = line.split(',')
        one_data.append(parts[Parameter])
    x = list(range(len(one_data)))
    plt.plot(x, one_data)
    plt.gca().invert_yaxis()
    plt.title(Parameter_Name)
    plt.show()

def three_draw():
    dr_a = []
    dr_b = []
    dr_c = []
    dr_d = []

    config = configparser.ConfigParser()
    config.read('configure.ini')
    a_num = int(config.get('Section1', 'a'))
    b_num = int(config.get('Section1', 'b'))
    c_num = int(config.get('Section1', 'c'))
    d_num = int(config.get('Section1', 'd'))
    a_name = config.get('Section1', 'a_name')
    b_name = config.get('Section1', 'b_name')
    c_name = config.get('Section1', 'c_name')
    d_name = config.get('Section1', 'd_name')
    topic = config.get('Section1', 'draw_name')

    with open(txt_data, "r", encoding='utf - 8') as file:
        lines = file.readlines()

    sort_lines = sorted(lines, key=get_hex_value)
    FRI_lines = sort_pick(sort_lines)

    for line in FRI_lines:
        parts = line.split(',')
        if a_num != -1:
            dr_a.append(float(parts[a_num]) if parts[a_num].strip() else 0)
        if b_num != -1:
            dr_b.append(float(parts[b_num]) if parts[b_num].strip() else 0)
        if c_num != -1:
            dr_c.append(float(parts[c_num]) if parts[c_num].strip() else 0)
        if d_num != -1:
            dr_d.append(float(parts[d_num]) if parts[d_num].strip() else 0)

    all_values = []
    if a_num != -1:
        all_values.extend(dr_a)
    if b_num != -1:
        all_values.extend(dr_b)
    if c_num != -1:
        all_values.extend(dr_c)
    if d_num != -1:
        all_values.extend(dr_d)

    if all_values:
        min_val = min(all_values)
        max_val = max(all_values)
        tick_values = [min_val + i * (max_val - min_val) / 9 for i in range(10)]
    else:
        tick_values = []

    fig,ax1 = plt.subplots()

    lines = []
    if a_num != -1:
        line1 = ax1.plot(dr_a, 'b-', label=a_name)
        lines.extend(line1)
    if b_num != -1:
        line2 = ax1.plot(dr_b, 'r-', label=b_name)
        lines.extend(line2)
    if c_num != -1:
        line3 = ax1.plot(dr_c, 'g-', label=c_name)
        lines.extend(line3)
    if d_num != -1:
        line4 = ax1.plot(dr_d, 'y-', label=d_name)
        lines.extend(line4)

    if tick_values:
        ax1.set_yticks(tick_values)

    plt.title(topic)

    plt.title(topic)
    if lines:
        labels = [line.get_label() for line in lines]
        plt.legend(lines, labels)

    plt.show()


def menu_show():
    delete_widget()
    def txt():
        global txt_data
        txt_entry.delete(0, tk.END)
        txt_data = filedialog.askopenfilename(filetypes = [('txt', '*.txt')])
        txt_entry.insert(0, txt_data)

    txt_entry = tk.Entry(window, width = 30)
    txt_entry.place(relx = 0.3, rely = 0.02)
    txt_choice = tk.Button(window, text = '文件选取', pady = 1, padx = 1, command = txt, width = 10)
    txt_choice.place(relx = 0.55, rely = 0.01)
    one_draw_but = tk.Button(window, text = '单折线绘图', pady = 1, padx = 1,command = one_draw, width = 10)
    one_draw_but.place(relx = 0.4, rely = 0.1)
    create_tooltip(one_draw_but, "说明：\n1.打开configure.ini，配置Parameter(位置)和Parameter_topic（标题）\n2.仅支持GTFRI报文")
    three_draw_but = tk.Button(window, text = '多折线绘图', pady = 1, padx = 1,command = three_draw, width = 10)
    three_draw_but.place(relx = 0.4, rely = 0.25)
    create_tooltip(three_draw_but, "说明：\n1.打开configure.ini，配置a/b/c/d(位置)和a_name/b_name/c_name/d_name(标题)\n2.仅支持GTFRI报文\n3.最多支持4条折线\n4.哪一条折线不需要就填-1")


#main_menu.add_command (label = "绘图", command = menu_show)

'''------------------------------------------------------------
报文排序功能设置

排序检查序列号

-------------------------------------------------------------'''
'''def get_key(item):
    return item[-5:]

def sort():
    delete_widget()
    filepath = filedialog.askopenfilename(filetypes = [('txt', '*.txt')])
    if filepath:
        with open(filepath, "r", encoding='utf-8') as file:   
            get_lines = file.readlines()
            sort_lines = sorted(get_lines, key = get_key)
            with open('sort.txt', "w", encoding='utf-8') as new_file:
                new_file.writelines(sort_lines)
        messagebox.showinfo("执行结果", "排序完成，请查询sort.txt文档!")
    else:
        messagebox.showinfo("执行结果", "请选择排序文件!")

def sort_re():
    sort_but = tk.Button(window, text = '排序', pady = 10, padx = 10, command = sort)
    sort_but.place(x = 25, y = 20)
    lack_but = tk.Button(window, text = '检查序列号遗漏', pady = 10, padx = 10)
    lack_but.place(x = 0, y = 80) 


#main_menu.add_command (label = "排序", command = sort_re)'''


'''------------------------------------------------------------
惯导功能设置


-------------------------------------------------------------'''
def get_key(item):
    return item[-5:]

def convert_to_degree(coordinate_str):
    parts = coordinate_str[:-1].split(',')
    if parts[0] == '':
        return None
    value = float(parts[0])
    degrees = int(value / 100)
    minutes = value - degrees * 100
    decimal_degree = degrees + minutes / 60
    return decimal_degree

def get_hex_value(s):
    # 提取字符串末尾的十六进制部分
    s = s.strip()
    # 检查字符串是否为空或者是否以特定错误提示开头
    if not s or s.startswith("Warning:") or s.startswith("GET"):
        return None
    try:
        # 分割字符串并取最后一部分，同时移除首尾的 $ 和换行符
        hex_part = s.split(',')[-1].strip('$\n')
        # 将十六进制字符串转换为整数
        return int(hex_part, 16)
    except (ValueError, IndexError):
        # 如果转换失败或者分割后没有元素，返回 None
        return None

def sort(lines):
    valid_lines = [line for line in lines if get_hex_value(line) is not None]
    sort_lines = sorted(valid_lines, key=get_hex_value)
    return sort_lines


def check_lack():
    with open('sort_file.txt', "r", encoding='utf - 8') as file:
        lines = file.readlines()
    hex_values = [get_hex_value(line) for line in lines if get_hex_value(line) is not None]
    hex_values.sort()
    missing_sequences = []
    if var.get() == 0:
        for i in range(1, len(hex_values)):
            diff = hex_values[i] - hex_values[i - 1]
            if diff > 1:
                    # 计算丢失的序列号范围
                missing_sequences.append(hex_values[i])
                #print(missing_sequences)
        var.set(0)
    else:
        for i in range(1, len(hex_values)):
            diff = hex_values[i] - hex_values[i - 1]
            if diff > 2 :
                    # 计算丢失的序列号范围
                missing_sequences.append(hex_values[i])
        var.set(0)

    message = ""
    if missing_sequences:
        message = "以下是丢失的序列号位置：\n"
        for sequence in missing_sequences:
            message += hex(sequence) + "、"  # 正确将列表元素转换为十六进制字符串添加到消息中
    else:
        message = "未发现丢失的序列号。"
    messagebox.showinfo("执行结果", message)


def sort_pick(gt_fri_lines):
    new_lines = []
# 读取+RESP:GTFRI文件
    valid_lines = [line for line in gt_fri_lines if get_hex_value(line) is not None]
    sort_fri_lines = sorted(valid_lines, key=get_hex_value)

    for line in sort_fri_lines:
        new_line = None
        try:
            if '+RESP:GTFRI' in line:
                index = line.index('+RESP:GTFRI')
                new_line = line[index:].lstrip()
            elif '+BUFF:GTFRI' in line:
                index = line.index('+BUFF:GTFRI')
                new_line = line[index:].lstrip()
        except ValueError:
            continue
        if new_line:
            new_lines.append(new_line)
    return new_lines

def rmc_pick(lines):
    results = []
    for nmea_line in lines:
        if "GNRMC" in nmea_line:
            start_index = nmea_line.find("GNRMC")
            nmea_line = nmea_line[start_index:]
            parts = nmea_line.split(',')
            latitude = convert_to_degree(parts[3].strip())  # 纬度
            longitude = convert_to_degree(parts[5].strip())
            parts[3] = str(latitude)
            parts[5] = str(longitude)
            new_nmea_line = ','.join(parts)
            results.append(new_nmea_line)

    return results


def check_time():
    time_list = []
    with open('sort_file.txt', "r", encoding='utf - 8') as file:
        lines = file.readlines()
    prev_time = None
    try:
        interval = int(time_Entry.get())
        if interval:
            pass
    except Exception as e:
        messagebox.showinfo("执行结果", "请先输入FRI上报间隔Sendtime！")

    max_time_difference = 0  # 存储最大时间差
    max_time_difference_line = None  # 存储最大时间差对应的行

    for line in lines:
        fri_match = re.search(r"(:GTFRI,.*?)$", line)
        if fri_match:
            time = fri_match.group(0).split(',')[-2].strip('\n')
            count = fri_match.group(0).split(',')[-1].strip('\n')
            current_time = datetime.strptime(time, '%Y%m%d%H%M%S')
            if prev_time:
                time_difference = (current_time - prev_time).total_seconds()
                if time_difference < 0:
                    messagebox.showinfo("执行结果", f"时间出现倒退，{count}")
                    break
                if time_difference != interval and time_difference!= 0:
                    time_list.append(time_difference)
                    if max_time_difference == 0 or time_difference > max_time_difference:  # 比较并更新最大时间差及对应的行
                        max_time_difference = time_difference
                        max_time_difference_line = line
                elif time_difference == interval:
                    pass
            prev_time = current_time
    if max_time_difference:
        messagebox.showinfo("执行结果", f"时间出现误差，最大偏差{max_time_difference}s，序列号为{max_time_difference_line.split(',')[-1]}")
    else:
        messagebox.showinfo("执行结果", "GTFRI报文时间间隔正确！" )





def deal():
    results = []

    # 读取 NMEA 文件
    with open(filepath_nmea, "r", encoding='utf-8') as file:
        nmea_lines = file.readlines()
        rmc_lines = rmc_pick(nmea_lines)

    # 读取 FRI 文件
    with open(filepath_fri, "r", encoding='utf-8') as file:
        gt_fri_lines = file.readlines()
    fri_lines = sort_pick(gt_fri_lines)

    # 预处理 FRI 数据
    fri_time_dict = {}
    for gt_fri_line in fri_lines:
        parts = gt_fri_line.split(',')
        sub_string = parts[-2][8:14].strip()
        sub_string_datetime = datetime.strptime(sub_string, "%H%M%S")
        # 减 1 秒
        new_sub_string_datetime = sub_string_datetime - timedelta(seconds=1)
        sub_string_minus_1 = new_sub_string_datetime.strftime("%H%M%S")
        if sub_string_minus_1 not in fri_time_dict:
            fri_time_dict[sub_string_minus_1] = []
        fri_time_dict[sub_string_minus_1].append(gt_fri_line)

    # 处理 RMC 数据
    for nmea_line in rmc_lines:
        nmea_match = re.match(r'.*?(GNRMC),(\d{6}\.\d{2}),([A|V]),(\d{4}\.\d{5}),([NS]),(\d{5}\.\d{5}),([EW]),([\d.]*),([\d.]*).*', nmea_line)
        if nmea_match:
            nmea_utc_time = nmea_match.group(2)  # UTC 时间
            latitude = convert_to_degree(nmea_match.group(4))  # 纬度
            longitude = convert_to_degree(nmea_match.group(6))  # 经度
            heading = nmea_match.group(9)
            # 提取 NMEA UTC 时间的前 6 位（小时、分钟、秒）
            nmea_utc_time_prefix = nmea_utc_time[:6]
            if nmea_utc_time_prefix in fri_time_dict:
                for gt_fri_line in fri_time_dict[nmea_utc_time_prefix]:
                    results.append(f"{nmea_utc_time},{longitude},{latitude},{heading},{gt_fri_line.strip()}\n")

    # 写入输出文件
    with open('output_file.txt', "w") as file:
        file.writelines(results)
    messagebox.showinfo("执行结果", "成功，请查看output_file.txt!")
    sort_rmc_but.config(state=tk.NORMAL)

def start_deal():
    sort_rmc_but.config(state=tk.DISABLED)
    thread = threading.Thread(target=deal)
    thread.start()



def vectorized_geodesic(lat1, lon1, lat2, lon2):
        def geodist(lat1, lon1, lat2, lon2):
            return geodesic((lat1, lon1), (lat2, lon2)).meters
        vfunc = np.vectorize(geodist)
        return vfunc(lat1, lon1, lat2, lon2)


def cal_dr():
    distance = []
    first_point_distance = []
    marked_distances = []
    marked_indices = []
    target_distances  = [500, 1000, 1500, 2000]
    marked_targets = []

    with open('output_file.txt', "r", encoding='utf-8') as file:
        lines = file.readlines()

    config = configparser.ConfigParser()
    config.read('configure.ini')

    rmc_lon_pos = int(config.get('Section1', 'RMC_longitude'))
    rmc_lat_pos = int(config.get('Section1', 'RMC_latitude'))
    dr_lon_pos = int(config.get('Section1', 'DR_longitude'))
    dr_lat_pos = int(config.get('Section1', 'DR_latitude'))
    gnss_lon_pos = int(config.get('Section1', 'GNSS_longitude'))
    gnss_lat_pos = int(config.get('Section1', 'GNSS_latitude'))

    first_parts = lines[0].split(',')
    first_rmc_lat = float(first_parts[rmc_lat_pos])
    first_rmc_lon = float(first_parts[rmc_lon_pos])
    first_gnss_lon = float(first_parts[gnss_lon_pos])
    first_gnss_lat = float(first_parts[gnss_lat_pos])

    first_dist = vectorized_geodesic(first_rmc_lat, first_rmc_lon, first_gnss_lat, first_gnss_lon)
    messagebox.showinfo("距离计算", f"失锁前一个GNSS点与真值距离: {first_dist:.2f} meters")

    for i, line in enumerate(lines):
        parts = line.split(',')
        rmc_lon = float(parts[rmc_lon_pos])
        rmc_lat = float(parts[rmc_lat_pos])
        dr_lon = float(parts[dr_lon_pos])
        dr_lat = float(parts[dr_lat_pos])

        current_distance = vectorized_geodesic(first_rmc_lat, first_rmc_lon, rmc_lat, rmc_lon)
        first_point_distance.append(current_distance)

        dist = vectorized_geodesic(rmc_lat, rmc_lon, dr_lat, dr_lon)
        distance.append(dist)

        for target in target_distances:
            if target not in marked_targets and current_distance >= target:
                marked_distances.append(dist)
                marked_indices.append(i)
                marked_targets.append(target)
                #print(line)
                break

    with open('DR_DISTANCE_file.txt', "w", encoding='utf-8') as file:
        for dist in distance:
            file.write(str(dist) + '\n')

    plt.figure(figsize=(9.5, 3), dpi=100)
    plt.plot(distance)

    for index, dist in zip(marked_indices, marked_distances):
        plt.scatter(index, dist, color='red', marker='o')
        plt.annotate(f'{dist:.2f}m', (index, dist), textcoords="offset points", xytext=(0, 10), ha='center')

    plt.ylabel('DR_F9K-value')
    plt.xlabel('Time')
    plt.title("DR_F9K")
    plt.show()


def heading_draw():
    dr_a = []
    dr_b = []
    dr_c = []
    dr_d = []

    config = configparser.ConfigParser()
    config.read('configure.ini')
    a_num = int(config.get('Section1', 'heading_true'))
    b_num = int(config.get('Section1', 'azimuth'))
    c_num = int(config.get('Section1', 'dr_yaw'))
    d_num = int(config.get('Section1', 'dr_yaw_std'))
    a_name = config.get('Section1', 'heading_true_name')
    b_name = config.get('Section1', 'azimuth_name')
    c_name = config.get('Section1', 'dr_yaw_name')
    d_name = config.get('Section1', 'dr_yaw_std_name')
    topic = config.get('Section1', 'heading_draw_name')

    with open('output_file.txt', "r", encoding='utf - 8') as file:
        lines = file.readlines()

    for line in lines:
        parts = line.split(',')
        if a_num != -1:
            dr_a.append(float(parts[a_num]) if parts[a_num].strip() else 0)
        if b_num != -1:
            dr_b.append(float(parts[b_num]) if parts[b_num].strip() else 0)
        if c_num != -1:
            dr_c.append(float(parts[c_num]) if parts[c_num].strip() else 0)
        if d_num != -1:
            dr_d.append(float(parts[d_num]) if parts[d_num].strip() else 0)

    all_values = []
    if a_num != -1:
        all_values.extend(dr_a)
    if b_num != -1:
        all_values.extend(dr_b)
    if c_num != -1:
        all_values.extend(dr_c)
    if d_num != -1:
        all_values.extend(dr_d)

    if all_values:
        min_val = min(all_values)
        max_val = max(all_values)
        tick_values = [min_val + i * (max_val - min_val) / 9 for i in range(10)]
    else:
        tick_values = []


    fig,ax1 = plt.subplots()

    lines = []
    if a_num != -1:
        line1 = ax1.plot(dr_a, 'b-', label=a_name)
        lines.extend(line1)
    if b_num != -1:
        line2 = ax1.plot(dr_b, 'r-', label=b_name)
        lines.extend(line2)
    if c_num != -1:
        line3 = ax1.plot(dr_c, 'g-', label=c_name)
        lines.extend(line3)
    if d_num != -1:
        line4 = ax1.plot(dr_d, 'y-', label=d_name)
        lines.extend(line4)

    if tick_values:
        ax1.set_yticks(tick_values)

    plt.title(topic)

    plt.title(topic)
    if lines:
        labels = [line.get_label() for line in lines]
        plt.legend(lines, labels)

    plt.show()


def kepler_gl():
    config = configparser.ConfigParser()
    config.read('tool_collection_configure.ini')
    data = {}
    layers = []
    layer_colors = [[255, 0, 0], [255, 255, 0], [0, 0, 255], [0, 255, 128]]
    layer_labels = ["Data1", "Data2", "Data3", "Data4"]
    layer_sets = ["set1", "set2", "set3", "set4"]
    file_path = filedialog.askopenfilename(filetypes=[('txt', '*.txt')])
    try:
        with open(file_path, 'r', encoding='utf - 8') as file:
            lines = file.readlines()
        new_lines = sort_pick(lines)
        data_list = [line.strip().split(',') for line in new_lines]
        df = pd.DataFrame(data_list)
        rmc_lon_num = int(config.get('kepler', 'RMC_longitude'))
        rmc_lat_num = int(config.get('kepler', 'RMC_latitude'))
        gnss_lon_num = int(config.get('kepler', 'GNSS_longitude'))
        gnss_lat_num = int(config.get('kepler', 'GNSS_latitude'))
        dr_lon_num = int(config.get('kepler', 'DR_longitude'))
        dr_lat_num = int(config.get('kepler', 'DR_latitude'))
        mix_lon_num = int(config.get('kepler', 'mix_longitude'))
        mix_lat_num = int(config.get('kepler', 'mix_latitude'))
        lon_nums = [rmc_lon_num, gnss_lon_num, dr_lon_num, mix_lon_num]
        lat_nums = [rmc_lat_num, gnss_lat_num, dr_lat_num, mix_lat_num]

        for i in range(4):
            if lon_nums[i] != -1 and lat_nums[i] != -1:
                df_set = pd.DataFrame({
                    "lon": df[lon_nums[i]],
                    "lat": df[lat_nums[i]]
                })
                data[layer_sets[i]] = df_set

                layer = {
                    "id": f"point_layer_{i + 1}",
                    "type": "point",
                    "config": {
                        "dataId": layer_sets[i],
                        "label": layer_labels[i],
                        "color": layer_colors[i],
                        "columns": {
                            "lat": "lat",
                            "lng": "lon"
                        },
                        "radius": 5,
                        "sizeScale": 5,
                        "colorField": None,
                        "visConfig": {
                            "radius": 5,
                            "fixedRadius": True,
                            "radiusRange": [5, 5],
                            "sizeRange": [5, 5],
                            "opacity": 0.8,  # 设置点的不透明度
                            "strokeColor": [255, 255, 255],  # 设置点的边框颜色
                            "strokeWidth": 1,  # 设置点的边框宽度
                            "isVisible": True  # 确保图层可见
                        }
                    },
                    "visualChannels": {
                        "colorField": None,
                        "colorScale": "linear",
                        "sizeField": None,
                        "sizeScale": "linear"
                    }
                }
                layers.append(layer)

        config = {
            "version": "v1",
            "config": {
                "visState": {
                    "layers": layers,
                    "mapState": {
                        "longitude": 117.074702,
                        "latitude": 31.833923,
                        "zoom": 10,
                        "pitch": 0,
                        "bearing": 0
                    }
                }
            }
        }

        map_1 = KeplerGl(height=1600, data=data, config=config)
        map_1.save_to_html(file_name='kepler_map.html', data=data)
        messagebox.showinfo("提示", "Kepler文件生成成功！")

    except Exception as e:
        messagebox.showerror("错误", f"生成 HTML 文件时出现错误：{str(e)}")


def get_dr_fri():
    extracted_messages = []
    results = []
    try:
        with open('sort_file.txt', "r", encoding='utf-8') as file:
            lines = file.readlines()

        for i in range(len(lines) - 1, -1, -1):
            if dr_time_Entry.get() in lines[i]:
                end_index = i
                break

        if end_index is not None:
            for i in range(end_index, -1, -1):
                if 'GTADR' in lines[i]:
                    index = lines[i].index('GTADR')
                    new_line = lines[i][index:].lstrip()
                    parts = new_line.split(',')
                    if parts[4] not in ['3', '4']:
                        messagebox.showerror("初始化失败", "失锁前的第一条GTADR状态不为3/4。")
                        return
                    gtadr_serial = lines[i].strip().split(',')[-1].rstrip('$')
                    start_index = i
                    break

        if start_index is not None and end_index is not None:
            extracted_messages = lines[start_index:end_index + 1]
        results = sort_pick(extracted_messages)

        with open('Dr_FRI.txt', 'w') as file:
            file.writelines(results)
        messagebox.showinfo("执行结果", "成功，请查看Dr_FRI.txt!")

    except Exception as e:
        messagebox.showerror("错误", f"提取出现错误：{str(e)}")



def guandao():
    delete_widget()
    choice_data = tk.LabelFrame(window, height = 700, width = 320, text="数据处理", relief = 'groove')
    choice_data.place(relx = 0, rely = 0)
    cal_data = tk.LabelFrame(window, height = 700, width = 660, text="数据处理", relief = 'groove')
    cal_data.place(relx = 0.34, rely = 0)


    fri_entry = tk.Entry(choice_data, width = 30)
    fri_entry.place(relx = 0.0, rely = 0.01)
    nmea_entry = tk.Entry(choice_data, width = 30)
    nmea_entry.place(relx = 0.0, rely = 0.068)



    def nmea():
        global filepath_nmea
        nmea_entry.delete(0, tk.END)
        filepath_nmea = filedialog.askopenfilename(filetypes = [('txt', '*.txt')])
        nmea_entry.insert(0, filepath_nmea)

    fri_but = tk.Button(choice_data, text = '报文选取', pady = 1, padx = 1, command = fri, width = 10)
    fri_but.place(relx = 0.7, rely = 0.0)
    create_tooltip(fri_but, "功能：选取需要处理的报文文件TXT格式")
    nmea_but = tk.Button(choice_data, text = 'NMEA选取', pady = 1, padx = 1, command = nmea, width = 10)
    nmea_but.place(relx = 0.7, rely = 0.06)
    create_tooltip(nmea_but, "功能：选取需要处理的NMEA文件TXT格式")
    global sort_rmc_but
    sort_rmc_but = tk.Button(choice_data, text = '排序合并', pady = 1, padx = 1, command = start_deal, width = 10)
    sort_rmc_but.place(relx = 0.3, rely = 0.13)
    create_tooltip(sort_rmc_but, "功能：以GTFRI报文sendtime-1为基准从NMEA语句中抽取对应UTC时间的RMC，组合成一条语句")
    label1 = tk.Label(choice_data,text='说明：选取NMEA文件和报文文件，排序并\n按对应时间抽取保存output_file.txt',justify='center',width=40,height=3,)
    label1.place(relx = 0.0, rely = 0.18)


    create_tooltip(sort_but, "功能：从报文选取的文件中将所有报文进行排序")
    label1 = tk.Label(choice_data,text='说明：选取报文文件，按序列号排序并保存sort_file.txt',justify='center',width=40,height=3,)
    label1.place(relx = 0.0, rely = 0.34)

    check_but = tk.Button(choice_data, text = '查漏', pady = 1, padx = 1, command = check_lack, width = 10)
    check_but.place(relx = 0.3, rely = 0.44)
    create_tooltip(check_but, "功能：检查报文序列号是否丢失并打印出来")
    label1 = tk.Label(choice_data,text='说明：按序列号检查！！！',justify='center',width=40,height=3,)
    label1.place(relx = 0.0, rely = 0.50)
    global var
    var = tk.IntVar()
    tk.Radiobutton(choice_data, text="过滤1", variable=var, value=1).place(relx=0.08, rely=0.445)

    check_sendtime = tk.Button(choice_data, text = '检查FRI Sendtime', pady = 1, padx = 1, command = check_time, width = 15)
    check_sendtime.place(relx = 0.6, rely = 0.60)
    create_tooltip(check_sendtime, "功能：检查FRI报文上报间隔是否正确\n操作流程：\n1.选取FRI报文文件\n2.先排序，点击'仅排序'功能\n3.设置上报间隔")
    label2 = tk.Label(choice_data,text='说明：请先排序后再检查Sendtime！！！',justify='center',width=40,height=3,)
    label2.place(relx = 0.0, rely = 0.65)
    label3 = tk.Label(choice_data,text='Sendtime Interval:',justify='center',width=15,height=3,)
    label3.place(relx = 0.0, rely = 0.58)
    global time_Entry
    time_Entry = tk.Entry(choice_data, width = 8)
    time_Entry.place(relx = 0.35, rely = 0.61)


    cal_dr_f9k = tk.Button(cal_data, text ='DR<->F9K', command =  cal_dr, pady = 1, padx = 1, width = 10)
    cal_dr_f9k.place(relx = 0.02, rely = 0.02)
    Heading_draw = tk.Button(cal_data, text = 'Heading绘图', command =  heading_draw, pady = 1, padx = 1, width = 10)
    Heading_draw.place(relx = 0.02, rely = 0.18)
    create_tooltip(Heading_draw, "功能：绘制Heading曲线\n制作流程：\n1.通过排序合并功能生成output_file.txt，FRI报文选取的是初始化开始到失锁结束\n2.配置congigure.ini文件中经纬度位置，不需要的经纬度配置-1")
    kepler_gl_but = tk.Button(cal_data, text = '制作kepler', command = kepler_gl, pady = 1, padx = 1, width = 10)
    kepler_gl_but.place(relx = 0.02, rely = 0.34)
    create_tooltip(kepler_gl_but, "功能：将经纬度转换为Kepler Map.\n制作流程：\n1.通过排序合并功能生成output_file.txt，FRI报文选取的是初始化开始到失锁结束\n2.配置congigure.ini文件中heading_true、heading_true_name、azimuth、azimuth_name、\n"
                   "dr_yaw、dr_yaw_name、dr_yaw_std、dr_yaw_std_name、heading_draw_name(标题)，不需要的配置-1\n")
    get_dr_fri_but = tk.Button(cal_data, text = '截取DR报文', command = get_dr_fri, pady = 1, padx = 1, width = 10)
    get_dr_fri_but.place(relx = 0.02, rely = 0.50)
    create_tooltip(get_dr_fri_but, "功能：截取惯导初始化开始到失锁结束后的fri报文\n操作流程：\n1.请先通过'仅排序'功能给报文排序\n2.填写结束的时间，如20250313081232\n3.截取后的FRI报文保存在DR_FRI.txt中")
    global dr_time_Entry
    dr_time_Entry = tk.Entry(cal_data, width = 15)
    dr_time_Entry.place(relx = 0.18, rely = 0.51)


#main_menu.add_command (label = "惯导功能", command = guandao)

def cal_time():
    time3_entry.delete(0, 'end')
    time4_entry.delete(0, 'end')
    time1 = datetime.strptime(time1_entry.get(), '%Y%m%d%H%M%S')
    time2 = datetime.strptime(time2_entry.get(), '%Y%m%d%H%M%S')
    if time1 < time2:
        time1, time2 = time2, time1
    difference = time1 - time2
    time3_entry.insert(0, str(int(difference.total_seconds())))

    total_seconds = int(difference.total_seconds())
    hours = total_seconds // 3600
    minutes = (total_seconds % 3600) // 60
    seconds = total_seconds % 60
    time_str = "{:02d}:{:02d}:{:02d}".format(hours, minutes, seconds)
    time4_entry.insert(0, time_str)




#main_menu.add_command (label = "时间计算", command = time_cal)

'''------------------------------------------------------------
Queclink_Protocol_Analysis


-------------------------------------------------------------'''
def protocol_analyze():
    delete_widget()
    #chrome_driver = None
    selected_format = tk.StringVar()
    selected_format.set("ASCII")  # 默认选择 ASCII 格式


    def pro_text():
        global txt_data
        txt_entry.delete(0, tk.END)
        txt_data = filedialog.askopenfilename(filetypes=[('txt', '*.txt')])
        txt_entry.insert(0, txt_data)
        return txt_data


    def get_chrome():
        url = url_entry.get()
        global chrome_driver
        script_dir = os.path.dirname(os.path.abspath(__file__))
        chrome_driver_path = os.path.join(script_dir, 'chromedriver.exe')
        service = Service(chrome_driver_path)
        chrome_driver = webdriver.Chrome(service=service)
        chrome_driver.get(url)


    def test_command(command):
        if selected_format.get() == "HEX":
            # 获取 ASCII 命令在 ascii_commands 列表中的索引
            index = ascii_commands.index(command)
            # 根据索引获取对应的 HEX 命令
            hex_command = f"{hex(hex_commads[index])[2:].upper()}"
            search_command = hex_command
        else:
            search_command = f"+RESP:{command}"

        longest_data = ""
        with open(txt_data, "r", encoding='utf-8') as file:
            lines = file.readlines()
            for line in lines:
                if search_command in line:
                    index = line.index(search_command)
                    new_line = line[index:].lstrip()
                    new_line = new_line.rstrip()
                    if len(new_line) > len(longest_data):
                        longest_data = new_line

        if longest_data:
            input_element = chrome_driver.find_element(By.ID, 'message')
            input_element.clear()
            input_element.send_keys(longest_data)
            but_element = chrome_driver.find_element(By.ID, 'test')
            but_element.click()
            time.sleep(2)
            out_element = chrome_driver.find_element(By.ID, 'errorList')
            output = out_element.text
            if output == "Error No.\nNo matching records found":
                button = button_widgets[command]
                button.config(bg="green")
            else:
                button = button_widgets[command]
                button.config(bg="red")


    def test_all_commands():
        for command in ascii_commands:
            test_command(command)


    ascii_commands = [
        "GTTOW", "GTSPD", "GTRTL", "GTDOG", "GTVGL", "GTHBM", "GTDIS", "GTIOB", "GTSOS", "GTIGL",
        "GTFRI", "GTEPS", "GTAIS", "GTLBC", "GTIDA", "GTGES", "GTGIN", "GTGOT", "GTSPE", "GTGSM",
        "GTINF", "GTGPS", "GTCID", "GTCSQ", "GTVER", "GTBAT", "GTIOS", "GTTMZ", "GTAIF", "GTGSV",
        "GTRSV", "GTBSV", "GTUVN", "GTCVN", "GTCSN", "GTATI", "GTQDA", "GTAUI", "GTCML", "GTBTI",
        "GTWRT", "GTSCS", "GTCIF", "GTPNA", "GTPFA", "GTPDP", "GTMPN", "GTMPF", "GTBTC", "GTJDR",
        "GTJDS", "GTGPJ", "GTSTC", "GTBPL", "GTASV", "GTANT", "GTIGN", "GTVGN", "GTIGF", "GTVGF",
        "GTIDN", "GTIDF", "GTGSS", "GTSTR", "GTSTP", "GTLSP", "GTFLA", "GTDOS", "GTTMP", "GTRMD",
        "GTPHL", "GTFTP", "GTEXP", "GTCLT", "GTCFU", "GTSVR", "GTRTP", "GTPNR", "GTPFR", "GTLBA",
        "GTWPB", "GTCRA", "GTBCS", "GTBDS", "GTASC", "GTHBE", "GTAUR", "GTDOM", "GTBAA", "GTBID",
        "GTBAR", "GTMON", "GTCRE", "GTAVC", "GTUFS", "GTCRG", "GTFSD", "GTPHD", "GTCRD", "GTACC",
        "GTDAT", "GTUDT", "GTTKS", "GTDTT", "GTCAN", "GTUPC", "GTUPD", "GTEUC", "GTEUD", "GTHBD",
        "GTSTT"
    ]

    hex_commads = [
        0x2B52535001, 0x2B52535009, 0x2B5253500B, 0x2B5253500C, 0x2B5253501A, 0x2B5253500F, 0x2B52535005, 0x2B52535006,
        0x2B5253500A, 0x2B52535010, 0x2B52535007, 0x2B52535004, 0x2B5253500E, 0x2B52535003, 0x2B52535011, 0x2B4556541C,
        0x2B52535014, 0x2B52535015, 0x2B52535065, 0x2B494E460A, 0x2B494E4601, 0x2B494E4602, 0x2B494E4604, 0x2B494E4605,
        0x2B494E4606, 0x2B494E4607, 0x2B494E4608, 0x2B494E4609, 0x2B494E46FF, 0x2B494E460B, 0x2B494E4615, 0x2B494E4616,
        0x2B494E460C, 0x2B494E460D, 0x2B494E4614, 0x2B415449, 0x2B494E46FF, 0x2B494E4617, 0x2B494E461A, 0x2B494E4618,
        0x2B494E461E, 0x2B494E461C, 0x2B494E4623, 0x2B45565401, 0x2B45565402, 0x2B4556540C, 0x2B45565403, 0x2B45565404,
        0x2B45565407, 0x2B45565414, 0x2B45565420, 0x2B4556546C, 0x2B45565408, 0x2B45565406, 0x2B494E4625, 0x2B4556540A,
        0x2B4556540D, 0x2B4556542D, 0x2B4556540E, 0x2B4556542E, 0x2B45565410, 0x2B45565411, 0x2B45565415, 0x2B45565417,
        0x2B45565418, 0x2B4556541D, 0x2B45565416, 0x2B4556541B, 0x2B4556541E, 0x2B45565421, 0x2B45565422, 0x2B45565426,
        0x2B45565423, 0x2B45565429, 0x2B4556542A, 0x2B45565449, 0x2B45565456, 0x2B45565430, 0x2B45565431, 0x2B4556543C,
        0x2B4556543D, 0x2B45565419, 0x2B45565434, 0x2B45565435, 0x2B4556542F, 0x2B45565433, 0x2B45565432, 0x2B4556544B,
        0x2B45565441, 0x2B45565443, 0x2B45565446, 0x2B4556540B, 0x2B45565466, 0x2B45565462, 0x2B45565425, 0x2B44415404,
        0x2B44415402, 0x2B44415401, 0x2B435244, 0x2B414343, 0x2B45565412, 0x2B455654FF, 0x2B455654FF, 0x2B4556541F,
        0x2B43414E01, 0x2B45565428, 0x2B4556540F, 0x2B45565428, 0x2B455654FF, 0x2B484244,0x2B45565409
    ]

    test_button = tk.Button(window, text='自测网页', pady=1, padx=1, width=10, command=get_chrome)
    test_button.place(relx=0.55, rely=0.1)
    create_tooltip(test_button, "填写测试网页网址，如：http://192.168.80.141:9000/protocolList")
    testall_button = tk.Button(window, text='测试所有报文', pady=1, padx=1, width=12, command=test_all_commands)
    testall_button.place(relx=0.01, rely=0.15)
    txt_entry = tk.Entry(window, width=30)
    txt_entry.place(relx=0.3, rely=0.02)
    url_entry = tk.Entry(window, width=30)
    url_entry.place(relx=0.3, rely=0.10)
    txt_choice = tk.Button(window, text='文件选取', pady=1, padx=1, width=10, command=pro_text)
    txt_choice.place(relx=0.55, rely=0.01)
    create_tooltip(txt_choice, "选取报文的TXT文件")

    # 添加格式选择下拉菜单
    format_label = tk.Label(window, text="选择测试格式:")
    format_label.place(relx=0.7, rely=0.02)
    format_combobox = ttk.Combobox(window, textvariable=selected_format, values=["ASCII", "HEX"])
    format_combobox.place(relx=0.8, rely=0.02)

    button_widgets = {}
    y = 0.2
    for i, command in enumerate(ascii_commands):
        button = tk.Button(window, text=command, pady=1, padx=1, width=8, command=lambda c=command: test_command(c))
        button.place(relx=(i % 10) * 0.1, rely=y + (i // 10) * 0.05)
        button_widgets[command] = button

#main_menu.add_command (label = "中间件", command = QPA)

'''------------------------------------------------------------
地图显示


-------------------------------------------------------------'''
def wgs84_to_gcj02(lng, lat):
    a = 6378245.0  # 地球长半轴
    ee = 0.00669342162296594323  # 扁率
    d_lat = transform_lat(lng - 105.0, lat - 35.0)
    d_lng = transform_lng(lng - 105.0, lat - 35.0)
    rad_lat = lat / 180.0 * math.pi
    magic = math.sin(rad_lat)
    magic = 1 - ee * magic * magic
    sqrtMagic = math.sqrt(magic)
    d_lat = (d_lat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * math.pi)
    d_lng = (d_lng * 180.0) / (a / sqrtMagic * math.cos(rad_lat) * math.pi)
    mgLat = lat + d_lat
    mgLng = lng + d_lng
    return [mgLng, mgLat]

def transform_lat(x, y):
    ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * math.sqrt(abs(x))
    ret += (20.0 * math.sin(6.0 * x * math.pi) + 20.0 * math.sin(2.0 * x * math.pi)) * 2.0 / 3.0
    ret += (20.0 * math.sin(y * math.pi) + 40.0 * math.sin(y / 3.0 * math.pi)) * 2.0 / 3.0
    ret += (160.0 * math.sin(y / 12.0 * math.pi) + 320 * math.sin(y * math.pi / 30.0)) * 2.0 / 3.0
    return ret

def transform_lng(x, y):
    ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * math.sqrt(abs(x))
    ret += (20.0 * math.sin(6.0 * x * math.pi) + 20.0 * math.sin(2.0 * x * math.pi)) * 2.0 / 3.0
    ret += (20.0 * math.sin(x * math.pi) + 40.0 * math.sin(x / 3.0 * math.pi)) * 2.0 / 3.0
    ret += (150.0 * math.sin(x / 12.0 * math.pi) + 300.0 * math.sin(x / 30.0 * math.pi)) * 2.0 / 3.0
    return ret

def fri_map_catch():
    map1_pos = []
    map_center = [31.840206, 117.128922]
    initial_zoom = 13

    # 创建地图对象
    map_obj = folium.Map(
        location=map_center,
        zoom_start=initial_zoom
    )

    gaode_tiles_url = 'https://wprd01.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scl=1&style=7'
    gaode_tiles = folium.TileLayer(
        tiles=gaode_tiles_url,
        attr='高德地图',
        name='高德地图',
        subdomains=['0', '1', '2', '3']
    )
    gaode_tiles.add_to(map_obj)

    cur_lng = -1
    cur_lon = -1
    distance_values = []
    filepath = filedialog.askopenfilename(filetypes = [('txt', '*.txt')])
    if filepath:
        with open(filepath, "r", encoding='utf - 8') as file:

            lines = file.readlines()

        new_lines = sort_pick(lines)
        config = configparser.ConfigParser()

        config.read('configure.ini')

        lon_index = int(config.get('Section1', 'GNSS_longitude'))

        lat_index = int(config.get('Section1', 'GNSS_latitude'))

        for line in new_lines:
            parts = line.split(',')
            if not parts[lat_index].strip() or not parts[lon_index].strip():
                continue

            serial = parts[-1]
            original_lng = float(parts[lon_index])
            original_lat = float(parts[lat_index])
            new_lng, new_lat = wgs84_to_gcj02(original_lng, original_lat)
            map1_pos.append([new_lat, new_lng])

            icon_size = '15px'  # 调整标记大小，这里设置为15px
            icon = folium.DivIcon(
                icon_size=(icon_size, icon_size),
                icon_anchor=(int(icon_size[:-2]) / 2, int(icon_size[:-2]) / 2),
                html=f'<div style="width: {icon_size}; height: {icon_size}; background-color: red; border-radius: 50%;"></div>'
            )

            tooltip_text = f"经纬度: ({original_lat}, {original_lng})"
            folium.Marker(
                location=[new_lat, new_lng],
                popup=f'序列号: {serial}',
                tooltip=tooltip_text,
                icon = icon
            ).add_to(map_obj)

            if cur_lng != -1 and cur_lat != -1:
                dist = vectorized_geodesic(original_lat, original_lng, cur_lat, cur_lng)
                cur_new_lng, cur_new_lat = wgs84_to_gcj02(cur_lng, cur_lat)
                point1 = [cur_new_lat, cur_new_lng]
                new_lng, new_lat = wgs84_to_gcj02(original_lng, original_lat)
                point2 = [new_lat, new_lng]
                folium.PolyLine(
                    [point1, point2],
                    color="blue",
                    weight=2.5,
                    opacity=1,
                    tooltip=f'距离: {dist:.2f} 米'
                ).add_to(map_obj)

            cur_lng = original_lng
            cur_lat = original_lat



        folium.LayerControl().add_to(map_obj)

        html_file = 'map1.html'
        map_obj.save(html_file)

        if os.path.exists(html_file):
            webbrowser.open('file://' + os.path.realpath(html_file))

    else:
        messagebox.showinfo("错误信息", "选择文件无效!")

def rmc_map_catch():
    map1_pos = []
    map_center = [31.840206, 117.128922]
    initial_zoom = 13

    # 创建地图对象
    map_obj = folium.Map(
        location=map_center,
        zoom_start=initial_zoom
    )

    gaode_tiles_url = 'https://wprd01.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scl=1&style=7'
    gaode_tiles = folium.TileLayer(
        tiles=gaode_tiles_url,
        attr='高德地图',
        name='高德地图',
        subdomains=['0', '1', '2', '3']
    )
    gaode_tiles.add_to(map_obj)

    cur_lng = -1
    cur_lon = -1
    distance_values = []
    filepath = filedialog.askopenfilename(filetypes = [('txt', '*.txt')])
    if filepath:
        with open(filepath, "r", encoding='utf - 8') as file:

            lines = file.readlines()

        new_lines = rmc_pick(lines)
        config = configparser.ConfigParser()

        config.read('configure.ini')

        lon_index = int(config.get('Section1', 'RMC_longitude'))

        lat_index = int(config.get('Section1', 'RMC_latitude'))

        for line in new_lines:
            parts = line.split(',')
            if not parts[lat_index].strip() or not parts[lon_index].strip():
                continue
            #serial = parts[-1]
            original_lng = convert_to_degree(parts[lon_index])
            original_lat = convert_to_degree(parts[lat_index])
            new_lng, new_lat = wgs84_to_gcj02(original_lng, original_lat)
            map1_pos.append([new_lat, new_lng])

            icon_size = '15px'  # 调整标记大小，这里设置为15px
            icon = folium.DivIcon(
                icon_size=(icon_size, icon_size),
                icon_anchor=(int(icon_size[:-2]) / 2, int(icon_size[:-2]) / 2),
                html=f'<div style="width: {icon_size}; height: {icon_size}; background-color: red; border-radius: 50%;"></div>'
            )

            tooltip_text = f"经纬度: ({original_lat}, {original_lng})"
            folium.Marker(
                location=[new_lat, new_lng],
                #popup=f'序列号: {serial}',
                tooltip=tooltip_text,
                icon = icon
            ).add_to(map_obj)

            if cur_lng != -1 and cur_lat != -1:
                dist = vectorized_geodesic(original_lat, original_lng, cur_lat, cur_lng)
                cur_new_lng, cur_new_lat = wgs84_to_gcj02(cur_lng, cur_lat)
                point1 = [cur_new_lat, cur_new_lng]
                new_lng, new_lat = wgs84_to_gcj02(original_lng, original_lat)
                point2 = [new_lat, new_lng]
                folium.PolyLine(
                    [point1, point2],
                    color="blue",
                    weight=2.5,
                    opacity=1,
                    tooltip=f'距离: {dist:.2f} 米'
                ).add_to(map_obj)

            cur_lng = original_lng
            cur_lat = original_lat



        folium.LayerControl().add_to(map_obj)

        html_file = 'map1.html'
        map_obj.save(html_file)

        if os.path.exists(html_file):
            webbrowser.open('file://' + os.path.realpath(html_file))

    else:
        messagebox.showinfo("错误信息", "选择文件无效!")

def map_show():
    delete_widget()
    fri_map_button = tk.Button(window, text='FRI Map\n选择文件', pady=1, padx=1, width=15, command=fri_map_catch)
    fri_map_button.place(relx=0.1, rely=0.1)
    create_tooltip(fri_map_button, "功能：将经纬度显示到地图上，并计算相邻点距离\n说明：选择的文件是报文的TXT文件，且只会显示GTFRI的点\n配置：1.配置congigure.ini文件中GNSS_longitude/GNSS_latitude(FRI中的位置，按','分割从RESP为0开始)")
    rmc_map_button = tk.Button(window, text='RMC Map\n选择文件', pady=1, padx=1, width=15, command=rmc_map_catch)
    rmc_map_button.place(relx=0.1, rely=0.3)
    create_tooltip(rmc_map_button, "功能：将经纬度显示到地图上，并计算相邻点距离\n说明：选择的文件是报文的TXT文件，且只会显示GNRMC的点\n配置：1.配置congigure.ini文件中RMC_longitude/RMC_latitude(RMC中的位置，按','分割从GNRMC为0开始")


#main_menu.add_command (label = "显示地图", command = map_show)

'''------------------------------------------------------------
地图显示


-------------------------------------------------------------'''
def send_feishu_message(webhook_url, message):
    """
    向飞书群聊发送消息
    :param webhook_url: 飞书机器人的 Webhook 地址
    :param message: 要发送的消息内容
    """
    headers = {
        "Content-Type": "application/json"
    }
    data = {
        "msg_type": "text",
        "content": {
            "text": message
        }
    }
    try:
        response = requests.post(webhook_url, headers=headers, data=json.dumps(data))
        response.raise_for_status()
    except requests.exceptions.RequestException as e:
        messagebox.showerror("错误", f'消息发送失败: {str(e)}')


def monitor_func():
    delete_widget()
    fri_map_button = tk.Button(window, text='监控异常重启', pady=1, padx=1, width=15, command=fri_map_catch)
    fri_map_button.place(relx=0.1, rely=0.1)
    create_tooltip(fri_map_button, "功能：监控设备异常重启")

#main_menu.add_command (label = "异常监控", command = monitor_func)

'''------------------------------------------------------------
真值系统


-------------------------------------------------------------'''
# 定义闰秒数，GPS时间和UTC时间之间的差值
leap_seconds = 18
# 定义GPS时间的起始日期
gps_epoch = datetime(1980, 1, 6)


# 初始化真实数据文件的路径
filepath_truth = ""
# 初始化消息数据文件的路径
filepath_messages = ""
filepath_nmeas = ""

#读取configure.ini配置
config = configparser.ConfigParser()
config.read('configure.ini')

#提取真值系统#INSPVAXA各参数值
week_index = int(config.get('TRUTH_VALUE_DEAL', 'week'))
second_index = int(config.get('TRUTH_VALUE_DEAL', 'seconds'))
truth_lon_index = int(config.get('TRUTH_VALUE_DEAL', 'truth_lon'))
truth_lat_index = int(config.get('TRUTH_VALUE_DEAL', 'truth_lat'))
truth_azimuth_index = int(config.get('TRUTH_VALUE_DEAL', 'truth_azimuth'))
north_speed_index = int(config.get('TRUTH_VALUE_DEAL', 'north_speed'))
east_speed_index = int(config.get('TRUTH_VALUE_DEAL', 'east_speed'))
truth_height_index = int(config.get('TRUTH_VALUE_DEAL', 'truth_height'))
pos_type_index = int(config.get('TRUTH_VALUE_DEAL', 'pos_type'))

#设备各参数值位置
utc_time_index = int(config.get('Device_data', 'utc_time'))
ins_time_index = int(config.get('Device_data', 'ins_time'))
gnss_accuracy_index = int(config.get('Device_data', 'gnss_accuracy'))

#真值数据与FRI数据整合后提取各参数值
#Heading部分
azimuth_num = int(config.get('HEADING', 'azimuth'))
truth_azimuth_num = int(config.get('HEADING', 'truth_azimuth'))
ins_yaw_num = int(config.get('HEADING', 'ins_yaw'))
nmea_heading_num = int(config.get('HEADING', 'nmea_heading'))
#Altitude部分
truth_height_num = int(config.get('Altitude', 'truth_height'))
altitude_num = int(config.get('Altitude', 'altitude'))
#Speed部分
truth_speed_num = int(config.get('Speed', 'truth_speed'))
gnss_speed_num = int(config.get('Speed', 'gnss_speed'))
nmea_speed_num = int(config.get('Speed', 'nmea_speed'))
#经纬度部分
gnss_accuracy_deal = int(config.get('GNSS', 'gnss_accuracy_deal'))
truth_lon_num = int(config.get('GNSS', 'truth_lon'))
truth_lat_num = int(config.get('GNSS', 'truth_lat'))
gnss_lon_num = int(config.get('GNSS', 'gnss_lon'))
gnss_lat_num = int(config.get('GNSS', 'gnss_lat'))
ins_lon_num = int(config.get('GNSS', 'ins_lon'))
ins_lat_num = int(config.get('GNSS', 'ins_lat'))
gnss_ins_lon_num = int(config.get('GNSS', 'gnss_ins_lon'))
gnss_ins_lat_num = int(config.get('GNSS', 'gnss_ins_lat'))
dr_motion_status_num = int(config.get('GNSS', 'dr_motion_status'))
motion_status_num = int(config.get('GNSS', 'motion_status'))
nmea_lon_num = int(config.get('GNSS', 'nmea_lon'))
nmea_lat_num = int(config.get('GNSS', 'nmea_lat'))
nmea_accuracy_num = int(config.get('GNSS', 'nmea_accuracy'))


#BIAS_GYRO部分惯导数据
bias_gyro_x_num = int(config.get('Bias_gyro', 'bias_gyro_x'))
bias_gyro_y_num = int(config.get('Bias_gyro', 'bias_gyro_y'))
bias_gyro_z_num = int(config.get('Bias_gyro', 'bias_gyro_z'))
#BIAS_ACC部分惯导数据
bias_acc_x_num = int(config.get('Bias_acc', 'bias_acc_x'))
bias_acc_y_num = int(config.get('Bias_acc', 'bias_acc_y'))
bias_acc_z_num = int(config.get('Bias_acc', 'bias_acc_z'))
#SCALE_GYRO部分惯导数据
scale_gyro_x_num = int(config.get('Scale_gyro', 'scale_gyro_x'))
scale_gyro_y_num = int(config.get('Scale_gyro', 'scale_gyro_y'))
scale_gyro_z_num = int(config.get('Scale_gyro', 'scale_gyro_z'))
#SCALE_ACC部分惯导数据
scale_acc_x_num = int(config.get('Scale_acc', 'scale_acc_x'))
scale_acc_y_num = int(config.get('Scale_acc', 'scale_acc_y'))
scale_acc_z_num = int(config.get('Scale_acc', 'scale_acc_z'))
#Protect_leval ublox的定位准确性等级
protect_leval_num = int(config.get('Protect_leval', 'protect_leval'))


"""---------------------------------------------------------------------------------------------"""

def label_text(text):
    """
    此函数用于更新结果标签的文本内容。
    :param text: 要显示在结果标签上的文本
    """
    result_label.config(text = text)

def clear_label():
    """
    此函数用于清空结果标签的文本内容。
    """
    result_label.config(text="")

def gps_to_utc(gps_week, gps_seconds):
    """
    此函数将GPS时间转换为UTC时间。
    :param gps_week: GPS周数
    :param gps_seconds: GPS周内的秒数
    :return: 格式化后的UTC时间字符串，格式为 "%Y%m%d%H%M%S"
    """
    # 计算总的GPS秒数
    total_gps_seconds = gps_week * 7 * 86400 + gps_seconds
    # 减去闰秒得到总的UTC秒数
    total_utc_seconds = total_gps_seconds - leap_seconds
    # 根据GPS起始日期和总的UTC秒数计算UTC时间
    utc_time = gps_epoch + timedelta(seconds=total_utc_seconds)
    # 格式化UTC时间为字符串
    formatted_utc_time = utc_time.strftime("%Y%m%d%H%M%S")
    return formatted_utc_time

def load_truth():
    """
    此函数用于让用户选择真实数据文件，并将文件路径显示在对应的输入框中。
    :return: 用户选择的真实数据文件的路径
    """
    global filepath_truth
    # 清空真实数据文件路径输入框
    truth_Entry.delete(0, tk.END)
    # 弹出文件选择对话框，让用户选择txt文件
    filepath_truth = filedialog.askopenfilename(filetypes=[('txt', '*.txt')])
    # 将选择的文件路径插入到输入框中
    truth_Entry.insert(0, filepath_truth)
    return filepath_truth

def load_messages():
    """
    此函数用于让用户选择消息数据文件，并将文件路径显示在对应的输入框中。
    :return: 用户选择的消息数据文件的路径
    """
    global filepath_messages
    # 清空消息数据文件路径输入框
    messages_Entry.delete(0, tk.END)
    # 弹出文件选择对话框，让用户选择txt文件
    filepath_messages = filedialog.askopenfilename(filetypes=[('txt', '*.txt')])
    # 将选择的文件路径插入到输入框中
    messages_Entry.insert(0, filepath_messages)
    return filepath_messages

def load_nmeas():
    """
    此函数用于让用户选择消息数据文件，并将文件路径显示在对应的输入框中。
    :return: 用户选择的消息数据文件的路径
    """
    global filepath_nmeas
    # 清空消息数据文件路径输入框
    nmea_Entry.delete(0, tk.END)
    # 弹出文件选择对话框，让用户选择txt文件
    filepath_nmeas = filedialog.askopenfilename(filetypes=[('txt', '*.txt')])
    # 将选择的文件路径插入到输入框中
    nmea_Entry.insert(0, filepath_nmeas)
    return filepath_nmeas

def speed_cal(north_speed, east_speed):
    """
        此函数将北向速度和东向速度转换为我们设备的speed，单位换算m/s -> km/h
        :param north_speed: 北向速度m/s
        :param east_speed: 东向速度m/s
        :return: 返回计算的速度值
        """
    speed = math.sqrt(north_speed ** 2 + east_speed ** 2) * 3.6
    return speed

def ins_time_cal(time):
    """
        此函数将北向速度和东向速度转换为我们设备的speed，单位换算m/s -> km/h
        :param time: 时间戳
        :return: 返回当前时间
        """
    rounded_time = round(time)
    ins_time = datetime.fromtimestamp(rounded_time) - timedelta(hours=8) #减去时区
    format_ins_time = ins_time.strftime("%Y%m%d%H%M%S")
    return format_ins_time


def gnss_accuracy_hex(gnss_accuracy):
    """
        此函数将gnss_accuracy转换成二进制并返回高低字节的十进制
        :param gnss_accuracy: 获取设备报文的gnss_accuracy
        :return: 返回高低字节的十进制
    """
    binary_str = bin(gnss_accuracy)[2:].zfill(8)
    low_four = binary_str[4:]
    high_four = binary_str[:4]

    low_four_decimal = int(low_four, 2)
    high_four_decimal = int(high_four, 2)

    return low_four_decimal, high_four_decimal

def gnss_accuracy_hex_list(gnss_accuracy_list):
    """
        此函数将gnss_accuracy转换成二进制并返回高低字节的十进制
        :param gnss_accuracy: 获取设备报文的gnss_accuracy
        :return: 返回高低字节的十进制
    """
    low_four_decimal_list = []
    high_four_decimal_list = []
    for gnss_accuracy in gnss_accuracy_list:
        binary_str = bin(gnss_accuracy)[2:].zfill(8)
        low_four_decimal = int(binary_str[-4:], 2)
        high_four_decimal = int(binary_str[:4], 2)
        low_four_decimal_list.append(low_four_decimal)
        high_four_decimal_list.append(high_four_decimal)

    return low_four_decimal_list, high_four_decimal_list


def info_match():
    """
        此函数将GTFRI报文与真值数据按时间匹配成一条语句，如果定位成功，则按utc时间匹配，如果定位失败则按INS时间匹配
        :param filepath_messages: 设备报文
        :param filepath_truth: 真值数据
        :results: 整合的数据统一保存在truth_data_match.txt文件中
    """
    truth_lines = [] # 初始化用于存储按照时间分组的消息行的字典
    time_dict = {} # 初始化用于存储最终匹配结果的列表
    results = []

    try:
        with open(filepath_truth, 'r', encoding='utf-8') as file:
        #提取真值数据中的#INSPVAXA
            lines = file.readlines()
            for line in lines:
                if '#INSPVAXA' in line:
                    truth_lines.append(line)

    except FileNotFoundError:
        # 如果文件未找到，弹出错误消息框显示错误信息
        messagebox.showerror("错误", f"文件 {filepath_truth} 未找到")
    except Exception as e:
        # 如果打开文件时出现其他错误，弹出错误消息框显示错误信息
        messagebox.showerror("错误", f"文件打开错误：{str(e)}")

    # 以只读模式打开消息文件，并提取GTFRI报文，排序
    if filepath_messages:
        with open(filepath_messages, 'r', encoding='utf-8') as file:
            lines = file.readlines()
            mess_fri = sort_pick(lines)
        for line in mess_fri:
            parts = line.split(',')
            if parts[-2].startswith('2020') or not parts[utc_time_index]:
                continue
            else:
                time1 = datetime.strptime(parts[-2].strip(), '%Y%m%d%H%M%S')
                time2 = datetime.strptime(parts[utc_time_index].strip(), '%Y%m%d%H%M%S')
                total_seconds = (time1 - time2).total_seconds()
                timezone = int(total_seconds / 3600)
                break
        print("timezone: ", timezone)
    elif filepath_nmeas:
        with open(filepath_nmeas, 'r', encoding='utf-8') as file:
            lines = file.readlines()
            nmea_lines = rmc_pick(lines)
    else:
        messagebox.showerror("错误","请选择对比的文件！")


    try:
        if filepath_messages:
        # 遍历设备报文，提取GTFRI报文
            for gt_fri_line in mess_fri:
                fri_index = gt_fri_line.find('+')
                fri_string = gt_fri_line[fri_index : ]
                #去除时间戳
                parts = fri_string.split(',')
                sendtime = parts[-2].strip()
                gnss_utc_time = parts[utc_time_index].strip()
                gnss_accuracy = int(parts[gnss_accuracy_index])
                low_four_decimal, high_four_decimal = gnss_accuracy_hex(gnss_accuracy)
                try:
                    if CheckVar7.get() == 1:
                        if ins_time_index != -1:
                            ins_time = ins_time_cal(float(parts[ins_time_index])).strip()
                            #当定位精度低4位为0时使用惯导时间匹配，当不为0时使用UTC时间匹配
                            if low_four_decimal == 0:
                                sub_string = ins_time
                            else:
                                sub_string = gnss_utc_time
                        else:
                            sub_string = gnss_utc_time
                    else:
                        if low_four_decimal == 0:
                            if timezone != 0:
                                time_obj = datetime.strptime(sendtime, '%Y%m%d%H%M%S')
                                new_time_obj = time_obj - timedelta(hours=timezone)
                                new_sendtime = new_time_obj.strftime('%Y%m%d%H%M%S')
                                sub_string = new_sendtime
                            else:
                                sub_string = sendtime
                        else:
                            sub_string = gnss_utc_time

                except Exception as e:
                    messagebox.showerror("错误", f"{str(e)}")
                    result_text = '非惯导项目，请检查configure.ini中[Device_data]的ins_time需设置为-1；惯导项目请检查是否包含非惯导项目报文'
                    label_text(result_text)
                #print(sub_string)
                # 如果该时间字符串不在字典中，如果不在字典中则为该键创建一个空列表
                if sub_string not in time_dict:
                    time_dict[sub_string] = []
                # 将该行添加到对应时间字符串的列表中
                time_dict[sub_string].append(gt_fri_line)

        elif filepath_nmeas:
            for nmea_line in nmea_lines:
                parts = nmea_line.split(',')
                time = parts[1].split('.')[0]
                date = parts[9]
                year = "20" + date[4:6]
                new_date = year + date[2:4] + date[0:2]
                utc_time = new_date + time

                if utc_time not in time_dict:
                    time_dict[utc_time] = []
                # 将该行添加到对应时间字符串的列表中
                time_dict[utc_time].append(nmea_line)
        else:
            messagebox.showerror("错误","请选择对比文件，且NMEA文件与报文文件不能同时勾选！")

        pva_lines = []
        # 遍历包含 '#INSPVAXA' 的行
        for truth_line in truth_lines:
            # 按逗号分割行
            parts = truth_line.split(',')
            heading = float(parts[truth_azimuth_index])

            week = float(parts[week_index])
            seconds = float(parts[second_index])
            truth_utc_time = gps_to_utc(week, seconds) # 调用 gps_to_utc 函数将 GPS 时间转换为 UTC 时间

            lon_pos = float(parts[truth_lon_index])
            lat_pos = float(parts[truth_lat_index])

            north_speed = float(parts[north_speed_index])
            east_speed = float(parts[east_speed_index])
            speed = speed_cal(north_speed, east_speed) #调用speed_cal函数将真值速度转换为设备速度
            pos_type = parts[pos_type_index]

            truth_height = float(parts[truth_height_index])

            new_parts = parts.copy()
            new_parts[5] = truth_utc_time
            pva_line = ','.join(new_parts)
            pva_lines.append(pva_line)
            # 如果转换后的 UTC 时间在字典中
            if truth_utc_time in time_dict:
                # 遍历该时间对应的所有消息行
                for gt_fri_line in time_dict[truth_utc_time]:
                    # 将匹配的信息组合成一行，并添加到结果列表中
                    results.append(
                        f"{pos_type},{week},{seconds},{truth_utc_time},{heading},{lon_pos},{lat_pos},{speed},{truth_height},{gt_fri_line.strip()}\n"
                    )
            else:
                continue

        if start_time_entry.get():
            for start_index, line in enumerate(results):
                parts = line.split(',')
                if start_time_entry.get() == parts[3]:
                    break
            for end_index, line in enumerate(results):
                parts = line.split(',')
                if end_time_entry.get() == parts[3]:
                    break
            results = results[start_index: end_index + 1]

        # 以写入模式打开 'truth_data_match.txt' 文件，使用 UTF-8 编码
        with open('truth_data_match.txt', 'w', encoding='utf-8') as file:
            # 将结果列表中的所有行写入文件
            file.writelines(results)
        with open('pva_time_convert.txt', 'w', encoding='utf-8') as file:
            # 将结果列表中的所有行写入文件
            file.writelines(pva_lines)

        # 生成匹配成功的提示信息
        result_text = 'FRI文件与真值文件时间匹配成功，综合数据见truth_data_match.txt'
        # 调用 label_text 函数显示提示信息
        label_text(result_text)

    except Exception as e:
        # 如果匹配过程中出现错误，弹出错误消息框显示错误信息
        messagebox.showerror("错误", f"info_match()匹配错误：{str(e)}")


def heading_deal():
    """
        此函数处理文件路径，并调用info_match函数
        :return:无
    """
    clear_label()
    if not filepath_truth:
        messagebox.showerror("错误", f"请先选择相关数据处理文件路径！！！")
        return
    elif filepath_messages or filepath_nmeas:
        info_match()
    else:
        messagebox.showerror("错误", f"2请先选择相关数据处理文件路径！！！")
        return



def heading_catch():
    """
        此函数将各航向角提取出来
        :return: 返回航向角
    """
    azimuth = []
    truth_azimuth = []
    ins_yaw = []

    with open('truth_data_match.txt', 'r', encoding='utf-8') as file:
    #打开文件
        lines = file.readlines()

    for line in lines:
    #从打开的文件中抽取对应的航向角
        parts = line.split(',')
        if CheckVar1.get() == 1 and truth_azimuth_num != -1:
            truth_azimuth.append(float(parts[truth_azimuth_num]))
        if CheckVar2.get() == 1 and azimuth_num != -1:
            if filepath_nmeas:
                azimuth.append(float(parts[nmea_heading_num]))
            else:
                azimuth.append(float(parts[azimuth_num]))
        if CheckVar3.get() == 1 and ins_yaw_num != -1:
            ins_yaw.append(float(parts[ins_yaw_num]))

    return azimuth, truth_azimuth, ins_yaw


def tru_heading_show():
    """
        此函数处理truth_data_match.txt文件，显示各航向角图
        :return:无
    """
    clear_label()
    global CALL_TRUTH_GNSS_ERROR_CAL
    CALL_TRUTH_GNSS_ERROR_CAL = False

    azimuth, truth_azimuth, ins_yaw = heading_catch()
    #调用heading_catch获取各航向角

    #将抽取的heading角显示出来
    if azimuth or truth_azimuth or ins_yaw:
        result_text = 'FRI文件与真值文件heading对应图加载完成！'
        label_text(result_text)

        fig, ax1 = plt.subplots()
        if azimuth:
            ax1.plot(azimuth, label='Azimuth')
        if truth_azimuth:
            ax1.plot(truth_azimuth, label='Truth Azimuth')
        if ins_yaw:
            ax1.plot(ins_yaw, label='INS Yaw')

        ax1.set_xlabel('Index')
        ax1.set_ylabel('Heading Value')
        ax1.set_title('Heading Comparison')
        ax1.legend()

        if CheckVar5.get() == 1 or CheckVar6.get() == 1:
            start_end_index = get_ins_index(flag = 0)
            for key, (start_index, end_index, recover_fixed_index, last_ponit_index, last_ponit_time, end_time, target_info) in start_end_index.items():
                last_ponit_angle = truth_azimuth[last_ponit_index]
                ax1.annotate(f'LPT: {last_ponit_time}\nHeading: {last_ponit_angle:.2f}°',
                             xy=(last_ponit_index, last_ponit_angle),
                             xytext=(10, 10),
                             textcoords='offset points',
                             arrowprops=dict(arrowstyle='->'))
        plt.show()
    else:
        messagebox.showerror("错误", "请先勾选需要处理的定位点！")

    CALL_TRUTH_GNSS_ERROR_CAL = True

def heading_error_deal():
    """
        此函数处理航向角，计算惯导航向角和设备航向角与真值航向角差值
        :return:返回计算的差值
    """
    truth_gnss_error_values = []
    truth_ins_error_values = []

    azimuth, truth_azimuth, ins_yaw = heading_catch()

    #计算两个航向角之间的差值，并将结果限定在 -180 度到 180 度的范围内
    if azimuth and truth_azimuth:
        truth_gnss_error = (np.array(azimuth) - np.array(truth_azimuth) + 180) % 360 - 180
        truth_gnss_error_values = truth_gnss_error.tolist()
    if ins_yaw and truth_azimuth:
        truth_ins_error = (np.array(ins_yaw) - np.array(truth_azimuth) + 180) % 360 - 180
        truth_ins_error_values = truth_ins_error.tolist()

    return truth_gnss_error_values, truth_ins_error_values

def heading_error_show():
    """
        此函数显示航向角差值
        :return:无
    """
    truth_gnss_error_values, truth_ins_error_values = heading_error_deal()

    if truth_gnss_error_values or truth_ins_error_values:
        result_text = '角度差计算完成！'
        label_text(result_text)

        fig, ax1 = plt.subplots()
        if truth_gnss_error_values:
            ax1.plot(truth_gnss_error_values, label='Truth GNSS Error')
        if truth_ins_error_values:
            ax1.plot(truth_ins_error_values, label='Truth INS Error')

        ax1.set_xlabel('Index')
        ax1.set_ylabel('Error Value')
        ax1.set_title('Heading Error')
        ax1.legend()
        plt.show()
    else:
        messagebox.showerror("错误", "请勾选定位点信息！")


def heading_dispersion():
    """
        此函数显示航向角的分布概率
        :return:无
    """
    truth_gnss_error_values, truth_ins_error_values = heading_error_deal()

    result_text = '角度差离散分布绘图成功！'
    label_text(result_text)

    def plot_error(error_values, title):
        min_value = min(error_values)
        max_value = max(error_values)

        bins = list(range(int(min_value // 5) * 5, int((max_value // 5) + 1) * 5 + 5, 5))
        # 绘制直方图
        n, bins, patches = plt.hist(error_values, bins=bins, edgecolor='black')

        plt.xlabel('Error Value (5-degree intervals)')
        plt.ylabel('Point')
        plt.title(title)
        tick_positions = bins[::4]
        tick_labels = [str(tick) for tick in tick_positions]
            # 设置横坐标标签旋转 45 度
        plt.xticks(tick_positions, tick_labels, rotation=45)

        total_points = len(error_values)
        probabilities = n / total_points

        # 在每个柱子上添加概率值
        for i in range(len(patches)):
            formatted_prob = f'{probabilities[i] * 100:.1f}%'
            if formatted_prob != '0.0%':
                plt.text(patches[i].get_x() + patches[i].get_width() / 2,
                         patches[i].get_height(),
                         formatted_prob,
                         ha='center', va='bottom')

        # 计算点的总数
        #total_points = len(error_values)
        # 在图表的右上角添加文本注释显示点的总数
        plt.text(0.95, 0.95, f'Total points: {total_points}',
                 horizontalalignment='right',
                 verticalalignment='top',
                 transform=plt.gca().transAxes)

    if truth_gnss_error_values or truth_ins_error_values:
        if truth_gnss_error_values:
        # 绘制第一个图
            plot_error(truth_gnss_error_values, 'Dispersion Plot of GNSS Heading')
            plt.show(block=False)
        if truth_ins_error_values:
        # 创建新的图形窗口
            plt.figure()
            # 绘制第二个图
            plot_error(truth_ins_error_values, 'Dispersion Plot of INS Heading')
            plt.show()
    else:
        messagebox.showerror("错误", "请勾选定位点信息！")

def gnss_pos_cal():
    """
        此函数提取经纬度信息
        :return:返回真值、GNSS、惯导、融合点经纬度信息
    """
    truth_pos = []
    gnss_pos = []
    ins_pos = []
    gnss_ins_pos = []
    gnss_accuracys = []

    file_name = file_choose()

    print(file_name)
    with open(file_name, 'r', encoding='utf-8') as file:
        lines = file.readlines()

    result_text = '地图加载完成！'
    label_text(result_text)

    for line in lines:
        parts = line.split(',')
        if CheckVar1.get() == 1 and truth_lon_num != -1:
            truth_lon = float(parts[truth_lon_num])
            truth_lat = float(parts[truth_lat_num])
            truth_pos.append([truth_lat, truth_lon])
        if CheckVar2.get() == 1 and  gnss_lon_num != -1:
            if filepath_nmeas:
                gnss_lon = float(parts[nmea_lon_num])
                gnss_lat = float(parts[nmea_lat_num])
                gnss_pos.append([gnss_lat, gnss_lon])
                gnss_accuracy = []
            else:
                gnss_accuracy = int(parts[gnss_accuracy_deal])
                gnss_accuracys.append(gnss_accuracy)
                gnss_lon = float(parts[gnss_lon_num])
                gnss_lat = float(parts[gnss_lat_num])
                gnss_pos.append([gnss_lat, gnss_lon])
        if CheckVar3.get() == 1 and  ins_lon_num != -1:
            ins_lon = float(parts[ins_lon_num])
            ins_lat = float(parts[ins_lat_num])
            ins_pos.append([ins_lat, ins_lon])
        if CheckVar4.get() == 1 and  gnss_ins_lon_num != -1:
            gnss_ins_lon = float(parts[gnss_ins_lon_num])
            gnss_ins_lat = float(parts[gnss_ins_lat_num])
            gnss_ins_pos.append([gnss_ins_lat, gnss_ins_lon])

    return truth_pos, gnss_pos, ins_pos, gnss_ins_pos, gnss_accuracys


def map_truth_show():
    """
        此函数将所有经纬度在地图上显示出来
        :return:无
    """
    truth_pos, gnss_pos, ins_pos, gnss_ins_pos , gnss_accuracys = gnss_pos_cal()

    map_center = [31.840206, 117.128922]
    initial_zoom = 15
    min_zoom = 10
    max_zoom = 19

    # 创建地图对象
    map_obj = folium.Map(
        location=map_center,
        min_zoom=min_zoom,
        max_zoom=max_zoom,
        zoom_start=initial_zoom
    )


    # 将插件添加到地图上




    tiandi_tiles_url = 'http://t0.tianditu.gov.cn/img_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=img&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=164da69c5eb830df43aaf4006cfe8f85'
    tiandi_cva_url = 'http://t0.tianditu.gov.cn/cva_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cva&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=164da69c5eb830df43aaf4006cfe8f85'

    tiandi_tiles = folium.TileLayer(
        tiles=tiandi_tiles_url,
        attr='天地地图',
        name='天地地图',
    )
    tiandi_tiles.add_to(map_obj)
    tiandi_tiles = folium.TileLayer(
        tiles=tiandi_cva_url,
        attr='天地地图',
        name='天地地图',
    )
    tiandi_tiles.add_to(map_obj)

    measure_control = MeasureControl()
    map_obj.add_child(measure_control)

    # 仅作地图点的种类标记
    truth_layer = folium.FeatureGroup(name='<span style="color: red;">Truth Points</span>')
    truth_layer_marker = folium.FeatureGroup(name='<span style="color: red;">Truth Points Marker</span>', show=False)
    gnss_layer = folium.FeatureGroup(name='<span style="color: blue;">GNSS Points</span>')
    ins_layer = folium.FeatureGroup(name='<span style="color: yellow;">INS Points</span>')
    gnss_ins_layer = folium.FeatureGroup(name='<span style="color: green;">GNSS_INS Points</span>')

    low_four_decimal, high_four_decimal = gnss_accuracy_hex_list(gnss_accuracys)

    # 添加 真值 位置标记
    if truth_pos:
        segments_truth = []
        current_segment_truth = []
        for point in truth_pos:
            if point != [0.0, 0.0]:
                current_segment_truth.append(point)
            else:
                # 如果 low 为 0 或者点为 [0.0, 0.0]，结束当前段并添加到 segments 列表
                if current_segment_truth:
                    segments_truth.append(current_segment_truth)
                    current_segment_truth = []

        if current_segment_truth:
            segments_truth.append(current_segment_truth)

        for segment_truth in segments_truth:
            if segment_truth:
                ant_path = AntPath(
                    locations=segment_truth,
                    color='red',
                    pulse_color='white',
                    dash_array=[10, 20],
                    delay=800
                )
                ant_path.add_to(gnss_layer)

    if truth_pos:
        for index, point in enumerate(truth_pos):
            lat, lon = point
            marker = folium.Marker(
                location=point,
                popup=f"Point {index + 1}: Latitude {lat}, Longitude {lon}",
            )
            marker.add_to(truth_layer_marker)

    # 添加 GNSS 位置标记
    if gnss_pos:
        segments_gnss = []
        current_segment_gnss = []
        for point, low in zip(gnss_pos, low_four_decimal):
            if low != 0 and point != [0.0, 0.0]:
                current_segment_gnss.append(point)
            else:
                # 如果 low 为 0 或者点为 [0.0, 0.0]，结束当前段并添加到 segments 列表
                if current_segment_gnss:
                    segments_gnss.append(current_segment_gnss)
                    current_segment_gnss = []

        if current_segment_gnss:
            segments_gnss.append(current_segment_gnss)

        for segment_gnss in segments_gnss:
            if segment_gnss:
                ant_path = AntPath(
                    locations=segment_gnss,
                    color='blue',
                    pulse_color='white',
                    dash_array=[10, 20],
                    delay=800
                )
                ant_path.add_to(gnss_layer)


    # 添加 惯导 位置标记
    if ins_pos:
        filtered_ins_pos = [point for point in ins_pos if point != [0.0, 0.0]]
        ant_path = AntPath(
            locations=filtered_ins_pos,
            color='yellow',
            pulse_color='white',
            dash_array=[10, 20],
            delay=800
        )
        ant_path.add_to(ins_layer)

    # 添加 融合点 位置标记
    if gnss_ins_pos:
        filtered_gnss_ins_pos = [point for point in gnss_ins_pos if point != [0.0, 0.0]]
        ant_path = AntPath(
            locations=filtered_gnss_ins_pos,
            color='green',
            pulse_color='white',
            dash_array=[10, 20],
            delay=800
        )
        ant_path.add_to(gnss_ins_layer)

    truth_layer.add_to(map_obj)
    truth_layer_marker.add_to(map_obj)
    gnss_layer.add_to(map_obj)
    ins_layer.add_to(map_obj)
    gnss_ins_layer.add_to(map_obj)

    folium.LayerControl().add_to(map_obj)

    map_file = 'Truth_map.html'
    map_obj.save(map_file)
    # 打开地图
    webbrowser.open('file://' + os.path.realpath(map_file))
def file_choose():
    button_value = on_radio_select()
    print(button_value)

    file_deal_choose = {
        1: 'overpass.txt',
        2: 'avenue.txt',
        3: 'high_speed.txt',
        4: 'opensky.txt',
        5: 'buildings.txt',
        6: 'tunnel.txt',
        7: 'trail.txt'
    }
    file_name = file_deal_choose.get(button_value, 'truth_data_match.txt')

    return file_name

def truth_gnss_error_cal(flag):
    """
        此函数将计算距离差和保存最大距离差前后各100个点
        :param flag: 主要是用来选择处理gnss_truth、ins_truth、gnss_ins_truth
        :return:返回计算的距离差以及距离差最大值的点的前后100个点
    """

    dis_errors = [] #存储距离差
    truth_points = [] #存储真值前后各100个点
    other_points = [] #存储GNSS点或惯导点或融合点前后各100个点
    mark_max_drift = []

    file_name = file_choose()

    with open(file_name, 'r', encoding='utf-8') as file:
        lines = file.readlines()

    gnss_fail_count = 0

    try:
        if not filepath_truth:
            messagebox.showerror("错误", "请选择真值文件路径！")
        elif not filepath_nmeas and not filepath_messages:
            messagebox.showerror("错误", "请选择设备文件文件路径！")
    except Exception as e:
        messagebox.showerror("错误", f"文件打开错误（truth_gnss_error_cal）：{str(e)}")

    for line in lines:
        parts = line.split(',')
        if filepath_nmeas and not filepath_messages:
            nmea_accuracy = parts[nmea_accuracy_num]
        else:
            gnss_accuracy = int(parts[gnss_accuracy_deal])
            low_four_decimal, _  = gnss_accuracy_hex(gnss_accuracy)


        tru_lon = float(parts[truth_lon_num])
        tru_lat = float(parts[truth_lat_num])
        if flag == 1 and CheckVar1.get() == 1 and CheckVar2.get() == 1 and gnss_lon_num != -1:
            if filepath_nmeas and nmea_accuracy == 'A':
                other_lon = float(parts[nmea_lon_num])
                other_lat = float(parts[nmea_lat_num])
            elif filepath_messages and low_four_decimal != 0:
                other_lon = float(parts[gnss_lon_num])
                other_lat = float(parts[gnss_lat_num])
            else:
                other_lon = 0
                other_lat = 0
                gnss_fail_count = gnss_fail_count + 1

        elif flag == 2 and CheckVar1.get() == 1 and CheckVar3.get() == 1 and ins_lon_num != -1:
            other_lon = float(parts[ins_lon_num])
            other_lat = float(parts[ins_lat_num])
        elif flag == 3 and CheckVar1.get() == 1 and CheckVar4.get() == 1 and gnss_ins_lon_num != -1:
            other_lon = float(parts[gnss_ins_lon_num])
            other_lat = float(parts[gnss_ins_lat_num])
        else:
            messagebox.showerror("错误", "请先勾选对应的定位点，并检查configure.ini中[GNSS]的配置！")

        result_text = '距离差计算完成！'
        label_text(result_text)
        if tru_lon == 0 or other_lon == 0:
            #如果真值未获取到经纬度的值则丢弃
            distance = 0
        else:
            dist = geodesic((other_lat, other_lon), (tru_lat, tru_lon)).kilometers * 1000
            #调用geodesic计算距离差
            distance = round(dist, 2)
            #保留2位小数
        dis_errors.append(distance)
        truth_points.append((tru_lat, tru_lon))
        other_points.append((other_lat, other_lon))
    print(gnss_fail_count)
    max_index = dis_errors.index(max(dis_errors))
    mark_max_drift.append(truth_points[max_index])
    mark_max_drift.append(other_points[max_index])

    print("max drift:", max(dis_errors))
    print("max index:", max_index)
    #查找距离差值最大的点的索引
    start_pos = max(0, max_index - 100)
    end_pos = min(len(dis_errors), max_index + 101)
    #计算前后100个点的索引
    selected_truth_points = truth_points[start_pos:end_pos]
    selected_other_points = other_points[start_pos:end_pos]

    return dis_errors, selected_truth_points, selected_other_points, gnss_fail_count, mark_max_drift

import matplotlib.pyplot as plt

def truth_gnss_error(flag):
    """
        此函数将距离差显示出来
        :param flag: 主要是用来选择显示gnss_truth、ins_truth、gnss_ins_truth的距离差
        :return:无
    """
    # 调用truth_gnss_error_cal获取距离差值
    error_values, _, _ , gnss_fail_count , _= truth_gnss_error_cal(flag)
    print("error_values", len(error_values))
    print("gnss_fail_count", gnss_fail_count)
    fig, ax1 = plt.subplots()
    ax1.plot(error_values, label='Position Error')
    ax1.set_xlabel('Index')
    ax1.set_ylabel('Error Value')
    ax1.set_title('Position Error')
    ax1.legend()

    max_error = max(error_values)
    average_error = sum(error_values) / (len(error_values) - gnss_fail_count)
    fixed_rate = ((len(error_values) - gnss_fail_count) / len(error_values))
    result_text = f'Max Drift：{max_error}m' + f'\nAverage：{average_error:.2f}m' + f'\nFixed Rate：{fixed_rate * 100:.2f}%'
    label_text(result_text)


    # 将最大距离差显示在图的右上角
    ax1.text(0.95, 0.95, f'Max Error: {max_error:.2f}\nAverage Error: {average_error:.2f}\nPSR: {fixed_rate * 100:.2f}%',
             horizontalalignment='right',
             verticalalignment='top',
             transform=ax1.transAxes)

    time_text = ""
    offset = 20
    ytext = 10
    if CheckVar5.get() == 1 or CheckVar6.get() == 1:
        start_end_index = get_ins_index(flag)
        for key, (start_index, end_index, recover_fixed_index, last_ponit_index, last_ponit_time, end_time, target_info) in start_end_index.items():
            if 0 <= last_ponit_index < end_index < len(error_values):
                if flag == 2:
                    ax1.plot(range(last_ponit_index, end_index + 1), error_values[last_ponit_index:end_index + 1], color='red')
                else:
                    ax1.plot()
                last_ponit_dist = error_values[last_ponit_index]
                ax1.annotate(f'LPT: {last_ponit_time}\nDist: {last_ponit_dist:.2f}m',
                    xy=(last_ponit_index, last_ponit_dist),
                    xytext=(10, ytext),
                    textcoords='offset points',
                    arrowprops=dict(arrowstyle='->'))
                ytext = ytext + offset
                # if end_index is not None and 0 <= end_index < len(error_values):
                #     end_dist = error_values[end_index]
                #     ax1.annotate(f'Time: {end_time}\nDist: {end_dist:.2f}m',
                #                  xy=(end_index, end_dist),
                #                  xytext=(10, 10),
                #                  textcoords='offset points',
                #                  arrowprops=dict(arrowstyle='->'))

                for target_distance, (index, target_time) in target_info.items():
                    if index is not None and 0 <= index < len(error_values):
                        target_info_text = f"{target_distance}: T={target_time}"
                        ax1.annotate(target_info_text,
                                     xy=(index, target_distance),  # 使用索引 index 作为 x 坐标
                                     xytext=(10, 10),  # 调整位置以避免重叠
                                     textcoords='offset points',
                                     arrowprops=dict(arrowstyle='->'))

    plt.show()

def gnss_error_discrete(flag):
    """
        此函数将获得的距离差转化成以5m间隔作距离概率分布
        :param flag: 主要是用来选择计算gnss_truth、ins_truth、gnss_ins_truth的距离差
        :return:无
    """
    #调用truth_gnss_error_cal来获取距离差
    error_values, _, _ , _ , _= truth_gnss_error_cal(flag)
    #print(error_values)
    #显示处理结果

    #计算直方图区间，并以5为步chang生成刻度
    min_value = min(error_values)
    max_value = max(error_values)
    bins = list(range(int(min_value // 5) * 5, int((max_value // 5) + 1) * 5 + 5, 5))

    #绘制直方图，n 是每个区间的频数，bins 是区间边界，patches 是直方图的图形对象
    n, bins, patches = plt.hist(error_values, bins=bins, edgecolor='black')
    plt.xlabel('Error Value(m)')
    plt.ylabel('Points')
    plt.title('Error Values')

    #计算总的点数，并计算每个区间的概率
    total_points = len(error_values)
    probabilities = n / total_points

    #过滤掉0.1%一下子的区间，避免因区间过多无法更直观的显示概率分布
    valid_indices = [] #储存>0.1%的概率值
    for i in range(len(probabilities)):
        if probabilities[i] * 100 >= 0.1:
            valid_indices.append(i)

    # 根据有效区间调整 x 轴范围
    if valid_indices:
        min_bin = bins[valid_indices[0]]
        max_bin = bins[valid_indices[-1] + 1]
        plt.xlim(min_bin, max_bin)

    # 在每个柱子上添加概率值
    for i in range(len(patches)):
        formatted_prob = f'{probabilities[i] * 100:.1f}%'
        if formatted_prob != '0.0%':
            #过滤0.0%的概率值显示
            plt.text(patches[i].get_x() + patches[i].get_width() / 2,
                     patches[i].get_height(),
                     formatted_prob,
                     ha='center', va='bottom',color='red')

    # 在图表的右上角添加文本注释显示点的总数
    plt.text(0.95, 0.95, f'Total points: {total_points}',
             horizontalalignment='right',
             verticalalignment='top',
             transform=plt.gca().transAxes)

    sorted_error_values = sorted(error_values)
    index_50 = int(len(sorted_error_values) * 0.5)
    index_90 = int(len(sorted_error_values) * 0.9)
    index_95 = int(len(sorted_error_values) * 0.95)
    index_99 = int(len(sorted_error_values) * 0.99)
    diff_50 = sorted_error_values[index_50]
    diff_90 = sorted_error_values[index_90]
    diff_95 = sorted_error_values[index_95]
    diff_99 = sorted_error_values[index_99]
    plt.text(0.95, 0.90, f'> 99%: {diff_99:.2f}m',
             horizontalalignment='right',
             verticalalignment='top',
             transform=plt.gca().transAxes)
    plt.text(0.95, 0.85, f'> 95%: {diff_95:.2f}m',
             horizontalalignment='right',
             verticalalignment='top',
             transform=plt.gca().transAxes)
    plt.text(0.95, 0.80, f'> 90%: {diff_90:.2f}m',
             horizontalalignment='right',
             verticalalignment='top',
             transform=plt.gca().transAxes)
    plt.text(0.95, 0.75, f'> 50%: {diff_50:.2f}m',
             horizontalalignment='right',
             verticalalignment='top',
             transform=plt.gca().transAxes)
    result_text = f'CEP99：{diff_99}m' + f'\nCEP95：{diff_95}m' + f'\nCEP90：{diff_90}m' + f'\nCEP50：{diff_50}m'
    label_text(result_text)
    plt.show()

def gnss_error_max(flag):
    """
        此函数将最大距离点的前后100个点在地图上显示出来
        :param flag: 主要是用来选择显示gnss_truth、ins_truth、gnss_ins_truth的地图
        :return:无
    """
    #调用truth_gnss_error_cal获取要在地图上显示的点
    _,truth_points, other_points, _ , mark_max_drift= truth_gnss_error_cal(flag)

    result_text = 'GNSS最大误差前后100个点的地图显示成功！'
    label_text(result_text)

    map_center = [31.840206, 117.128922] #地图中心
    initial_zoom = 13 #地图缩放等级

    # 创建地图对象
    map_obj = folium.Map(
        location=map_center,
        zoom_start=initial_zoom
    )

    tiandi_tiles_url = 'http://t0.tianditu.gov.cn/img_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=img&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=164da69c5eb830df43aaf4006cfe8f85'
    tiandi_tiles = folium.TileLayer(
        tiles=tiandi_tiles_url,
        attr='天地地图',
        name='天地地图',
    )
    tiandi_tiles.add_to(map_obj)

    measure_control = MeasureControl()
    map_obj.add_child(measure_control)

    if mark_max_drift:
        for point in mark_max_drift:
            folium.Marker(
                location=point,
                popup=f'Max_Drift: Latitude {point[0]}, Longitude {point[1]}',
            ).add_to(map_obj)

    if truth_points:
        filtered_truth_pos = [point for point in truth_points if point != [0.0, 0.0]]
        ant_path = AntPath(
            locations=filtered_truth_pos,
            color='red',
            pulse_color='white',
            dash_array=[10, 20],
            delay=800
        )
        ant_path.add_to(map_obj)

    if other_points:
        filtered_other_pos = [point for point in other_points if point != [0.0, 0.0]]
        ant_path = AntPath(
            locations=filtered_other_pos,
            color='blue',
            pulse_color='white',
            dash_array=[10, 20],
            delay=800
        )
        ant_path.add_to(map_obj)

    map_file = 'max_gnss_error_map.html'
    map_obj.save(map_file)
    #自动打开浏览器显示地图
    webbrowser.open('file://' + os.path.realpath(map_file))

def bias_scale(flag):
    """
        此函数将bias_gyro、bias_acc、scale_gyro、scale_acc绘图显示
        :param flag: 通过不同的button传入的值来显示bias_gyro、bias_acc、scale_gyro、scale_acc图
        :return:无
    """

    x_values = []
    y_values = []
    z_values = []

    text_x = None
    text_y = None
    text_z = None

    with open('truth_data_match.txt', 'r', encoding='utf-8') as file:
        lines = file.readlines()

    for line in lines:
        #处理数据
        parts = line.split(',')
        if flag == 1 and bias_gyro_x_num != -1:
            # 显示bias_gyro图
            bias_gyro_x_value = float(parts[bias_gyro_x_num])
            x_values.append(bias_gyro_x_value)
            bias_gyro_y_value = float(parts[bias_gyro_y_num])
            y_values.append(bias_gyro_y_value)
            bias_gyro_z_value = float(parts[bias_gyro_z_num])
            z_values.append(bias_gyro_z_value)
            text_x = 'bias_gyro_x'
            text_y = 'bias_gyro_y'
            text_z = 'bias_gyro_z'
        elif flag == 2 and bias_acc_x_num != -1:
            # 显示bias_acc图
            bias_acc_x_value = float(parts[bias_acc_x_num])
            x_values.append(bias_acc_x_value)
            bias_acc_y_value = float(parts[bias_acc_y_num])
            y_values.append(bias_acc_y_value)
            bias_acc_z_value = float(parts[bias_acc_z_num])
            z_values.append(bias_acc_z_value)
            text_x = 'bias_acc_x'
            text_y = 'bias_acc_y'
            text_z = 'bias_acc_z'
        elif flag == 3 and scale_gyro_x_num != -1:
            # 显示scale_gyro图
            scale_gyro_x_value = float(parts[scale_gyro_x_num])
            x_values.append(scale_gyro_x_value)
            scale_gyro_y_value = float(parts[scale_gyro_y_num])
            y_values.append(scale_gyro_y_value)
            scale_gyro_z_value = float(parts[scale_gyro_z_num])
            z_values.append(scale_gyro_z_value)
            text_x = 'scale_gyro_x'
            text_y = 'scale_gyro_y'
            text_z = 'scale_gyro_z'
        elif flag == 4 and scale_acc_x_num != -1:
            # 显示scale_acc图
            scale_acc_x_value = float(parts[scale_acc_x_num])
            x_values.append(scale_acc_x_value)
            scale_acc_y_value = float(parts[scale_acc_y_num])
            y_values.append(scale_acc_y_value)
            scale_acc_z_value = float(parts[scale_acc_z_num])
            z_values.append(scale_acc_z_value)
            text_x = 'scale_acc_x'
            text_y = 'scale_acc_y'
            text_z = 'scale_acc_z'
        else:
            messagebox.showerror("错误", "请先配置configure.ini!")

    fig, ax1 = plt.subplots()
    ax1.plot(x_values, label = text_x)
    ax1.plot(y_values, label = text_y)
    ax1.plot(z_values, label = text_z)
    ax1.set_xlabel('Index')
    ax1.set_ylabel('Values')

    ax1.legend()
    if CheckVar5.get() == 1 or CheckVar6.get() == 1:
        start_end_index = get_ins_index(flag=0)
        for key, (
        start_index, end_index, recover_fixed_index, last_ponit_index, last_ponit_time, end_time, target_info) in start_end_index.items():
            x_value = x_values[last_ponit_index]
            ax1.annotate(f'LPT: {last_ponit_time}\nValue: {x_value:.2f}',
                         xy=(last_ponit_index, x_value),
                         xytext=(10, 10),
                         textcoords='offset points',
                         arrowprops=dict(arrowstyle='->'))
    plt.show()

def get_ins_index(flag):
    """
    从 truth_data_match.txt 文件中读取数据，并根据 last_point 时间计算失锁前一个点和恢复定位的索引
    """
    index_dict = {}
    # GTADR的UTC时间
    initial_time = initial_time_entry.get()

    target_distances = [200, 400, 600, 1000, 1200, 1400, 1600, 1800, 2000]
    marked_targets = []
    if flag == 2:
        error_values, _, _ , _, _ = truth_gnss_error_cal(flag)
    else:
        error_values = None

    with open('truth_data_match.txt', 'r', encoding='utf-8') as file:
        lines = file.readlines()

    try:
        # 将多次测试分开，获取每次的index_dict
        found_first_start = False
        i = 0
        while i < len(lines):
            line = lines[i]
            parts = line.split(',')
            truth_utc_time = parts[3].strip()

            if not found_first_start:
                if initial_time and truth_utc_time == initial_time:
                    start_index = i
                    found_first_start = True
                else:
                    i += 1
                    continue
            else:
                # 后续循环默认将当前行作为起始点
                start_index = i
                end_index = None
                last_ponit_time = None
                last_ponit_index = None  # 初始化last_ponit_index为None
                for k in range(i + 1, len(lines)):
                    if k >= len(lines):
                        break
                    new_parts = lines[k].split(',')

                    # 寻找Last Point的时间
                    gnss_accuracy = int(new_parts[gnss_accuracy_deal])
                    low_four_decimal, high_four_decimal = gnss_accuracy_hex(gnss_accuracy)
                    if CheckVar6.get() == 1 and CheckVar5.get() != 1 and low_four_decimal == 0:
                        ten_point = True
                        for j in range(k + 1, k + 11, 1):
                            if j >= len(lines):
                                ten_point = False
                                break
                            temp_parts = lines[j].split(',')
                            temp_gnss_accuracy = int(temp_parts[gnss_accuracy_deal])
                            temp_low_four_decimal, _ = gnss_accuracy_hex(temp_gnss_accuracy)
                            if temp_low_four_decimal != 0:
                                ten_point = False
                                break
                        if ten_point:
                            new_parts = lines[k - 1].split(',')
                            last_ponit_time = new_parts[3].strip()
                            last_ponit_index = k - 1  # 这里修正为 k - 1
                            break

                    if CheckVar5.get() == 1 and CheckVar6.get() != 1 and new_parts[0].strip() == "NONE":
                        ten_point = True
                        for j in range(k + 1, k + 60, 1):
                            if j >= len(lines):
                                ten_point = False
                                break
                            temp_parts = lines[j].split(',')
                            if temp_parts[0].strip() != "NONE":
                                ten_point = False
                                break
                        if ten_point:
                            for g in range(k, -1, -1):
                                prev_parts = lines[g].split(',')
                                prev_gnss_accuracy = int(prev_parts[gnss_accuracy_deal])
                                prev_low_four_decimal, _ = gnss_accuracy_hex(prev_gnss_accuracy)
                                if prev_low_four_decimal == 0:
                                    continue
                                else:
                                    last_ponit_time = prev_parts[3].strip()
                                    last_ponit_index = g
                                    break
                            break

                # 寻找出隧道或手动失锁结束后第50个点的index和时间
                for m in range(k + 10, len(lines)):
                    if m >= len(lines):
                        break
                    out_dr_parts = lines[m].split(',')
                    gnss_accuracy = int(out_dr_parts[gnss_accuracy_deal])
                    low_four_decimal, _ = gnss_accuracy_hex(gnss_accuracy)
                    if CheckVar6.get() == 1 and low_four_decimal != 0:
                        if m + 50 < len(lines):
                            end_index = m + 50
                        else:
                            end_index = m
                        out_dr_parts = lines[end_index].split(',')
                        end_time = out_dr_parts[3].strip()
                        for n in range(m, len(lines)):
                            if n >= len(lines):
                                break
                            recv_dr_parts = lines[n].split(',')
                            gnss_accuracy = int(recv_dr_parts[gnss_accuracy_deal])
                            low_four_decimal, _ = gnss_accuracy_hex(gnss_accuracy)
                            if low_four_decimal != 0:
                                recover_fixed_index = n
                                break
                        break
                    if CheckVar5.get() == 1 and out_dr_parts[0].strip() != "NONE":
                        if m + 50 < len(lines):
                            end_index = m + 50
                        else:
                            end_index = m
                        out_dr_parts = lines[end_index].split(',')
                        end_time = out_dr_parts[3].strip()
                        for n in range(m, len(lines)):
                            recv_dr_parts = lines[n].split(',')
                            gnss_accuracy = int(recv_dr_parts[gnss_accuracy_deal])
                            low_four_decimal, _ = gnss_accuracy_hex(gnss_accuracy)
                            if low_four_decimal != 0:
                                recover_fixed_index = n
                                break
                        break

                # 寻找误差在200-2000m的点的index和时间
                target_info = {}
                if error_values:
                    for target_distance in target_distances:
                        # 检查 end_index 是否为 None
                        if end_index is not None:
                            for index in range(start_index, end_index + 1):
                                # 假设 error_values 是一个列表，且与 lines 长度相同
                                if target_distance not in marked_targets and error_values[index] >= target_distance:
                                    target_time = lines[index].split(',')[3].strip()
                                    target_info[target_distance] = (index, target_time)
                                    break

                if end_index is not None:
                    if last_ponit_index is not None:  # 检查last_ponit_index是否已经被赋值
                        index_dict[f'last_point{start_index}'] = (start_index, end_index, recover_fixed_index, last_ponit_index, last_ponit_time, end_time, target_info)
                    else:
                        index_dict[f'last_point{start_index}'] = (start_index, end_index, None, last_ponit_time, end_time, target_info)  # 如果没有赋值，设为None
                    i = end_index + 1
                else:
                    i = i + 1
                # print(start_index, end_index)
    except FileNotFoundError:
        # 如果文件未找到，弹出错误消息框显示错误信息
        messagebox.showerror("错误", f"文件 truth_data_match.txt 未找到")
    except Exception as e:
        # 如果打开文件时出现其他错误，弹出错误消息框显示错误信息
        messagebox.showerror("错误", f"文件打开错误（get_ins_index）：{str(e)}")

    print("index_dict", index_dict)
    return index_dict

def speed_height_deal(flag):
    truth_speeds = []
    gnss_speeds = []
    speed_errors = []
    truth_heights = []
    gnss_altitudes = []
    height_errors = []

    with open('truth_data_match.txt', 'r', encoding='utf-8') as file:
        lines = file.readlines()

    for line in lines:
        parts = line.split(',')
        if CheckVar7.get() == 1:
            motion_status = parts[dr_motion_status_num]
            if motion_status.startswith('22'):
                truth_speed = float(parts[truth_speed_num])
                gnss_speed = float(parts[gnss_speed_num])
                speed_error = truth_speed - gnss_speed
                truth_height = float(parts[truth_height_num])
                gnss_altitude = float(parts[altitude_num])
                height_error = truth_height - gnss_altitude
                truth_speeds.append(truth_speed)
                gnss_speeds.append(gnss_speed)
                speed_errors.append(speed_error)
                truth_heights.append(truth_height)
                gnss_altitudes.append(gnss_altitude)
                height_errors.append(height_error)
            else:
                continue
        elif motion_status_num != -1:
            motion_status = parts[motion_status_num]
            if motion_status.startswith('22'):
                truth_speed = float(parts[truth_speed_num])
                gnss_speed = float(parts[gnss_speed_num])
                speed_error = truth_speed - gnss_speed
                truth_height = float(parts[truth_height_num])
                gnss_altitude = float(parts[altitude_num])
                height_error = truth_height - gnss_altitude
                truth_speeds.append(truth_speed)
                gnss_speeds.append(gnss_speed)
                speed_errors.append(speed_error)
                truth_heights.append(truth_height)
                gnss_altitudes.append(gnss_altitude)
                height_errors.append(height_error)
            else:
                continue
        else:
            truth_speed = float(parts[truth_speed_num])
            if filepath_nmeas:
                gnss_speed_jie = float(parts[nmea_speed_num])
                gnss_speed = gnss_speed_jie * 1.852
                speed_error = truth_speed - gnss_speed
                truth_height = 0
                gnss_altitude = 0
                height_error = 0
            else:
                gnss_speed = float(parts[gnss_speed_num])
                speed_error = truth_speed - gnss_speed
                truth_height = float(parts[truth_height_num])
                gnss_altitude = float(parts[altitude_num])
                height_error = truth_height - gnss_altitude
            truth_speeds.append(truth_speed)
            gnss_speeds.append(gnss_speed)
            speed_errors.append(speed_error)
            truth_heights.append(truth_height)
            gnss_altitudes.append(gnss_altitude)
            height_errors.append(height_error)


    if flag == 1:
        return truth_speeds, gnss_speeds, speed_errors
    elif flag == 2:
        return truth_heights, gnss_altitudes, height_errors

def speed_show():
    flag = 1
    if CheckVar1.get() == 1 and CheckVar2.get() == 1:
        truth_speeds, gnss_speeds, _ = speed_height_deal(flag)

        result_text = '速度数据处理完成！'
        label_text(result_text)

        fig, ax1 = plt.subplots()
        if truth_speeds:
            ax1.plot(truth_speeds, label='Truth Speed')
        else:
            messagebox.showerror("错误", "请检查configure.ini中motion_status位置是否正确！")
        if gnss_speeds:
            ax1.plot(gnss_speeds, label='GNSS Speed')
        else:
            messagebox.showerror("错误", "请检查configure.ini中motion_status位置是否正确！")

        ax1.set_xlabel('Index')
        ax1.set_ylabel('Speed Value')
        ax1.set_title('Speed')
        ax1.legend()
        if motion_status_num != -1:
            ax1.text(0.95, 0.95, 'Only for ignition motion',
                     horizontalalignment='right',
                     verticalalignment='top',
                     color='red',
                     transform=ax1.transAxes)
        plt.show()
    else:
        messagebox.showerror("错误","请先勾选需要处理的定位点！")

def speed_error_show():
    flag = 1
    if CheckVar1.get() == 1 and CheckVar2.get() == 1:
        _, _, speed_errors = speed_height_deal(flag)
        result_text = '速度误差数据处理完成！'
        label_text(result_text)

        fig, ax1 = plt.subplots()
        if speed_errors:
            ax1.plot(speed_errors, label='Speed Error')

        ax1.set_xlabel('Index')
        ax1.set_ylabel('Speed Error')
        ax1.set_title('Speed Error')
        ax1.legend()
        ax1.text(0.95, 0.95, 'Only for ignition motion',
                 horizontalalignment='right',
                 verticalalignment='top',
                 color='red',
                 transform=ax1.transAxes)
        plt.show()
    else:
        messagebox.showerror("错误","请先勾选需要处理的定位点！")

def height_show():
    flag = 2
    if CheckVar1.get() == 1 and CheckVar2.get() == 1:
        truth_heights, gnss_altitudes, _ = speed_height_deal(flag)

        result_text = '高度数据处理完成！'
        label_text(result_text)

        fig, ax1 = plt.subplots()
        if truth_heights:
            ax1.plot(truth_heights, label='Truth Altitude')
        if gnss_altitudes:
            ax1.plot(gnss_altitudes, label='GNSS Altitude')

        ax1.set_xlabel('Index')
        ax1.set_ylabel('Altitude Value')
        ax1.set_title('Altitude')
        ax1.legend()
        ax1.text(0.95, 0.95, 'Only for ignition motion',
                 horizontalalignment='right',
                 verticalalignment='top',
                 color='red',
                 transform=ax1.transAxes)
        plt.show()
    else:
        messagebox.showerror("错误","请先勾选需要处理的定位点！")

def height_error_show():
    flag = 2
    if CheckVar1.get() == 1 and CheckVar2.get() == 1:
        _, _, height_errors = speed_height_deal(flag)
        result_text = '速度误差数据处理完成！'
        label_text(result_text)

        fig, ax1 = plt.subplots()
        if height_errors:
            ax1.plot(height_errors, label='Altitude Error')

        ax1.set_xlabel('Index')
        ax1.set_ylabel('Altitude Error')
        ax1.set_title('Altitude Error')
        ax1.legend()
        ax1.text(0.95, 0.95, 'Only for ignition motion',
                 horizontalalignment='right',
                 verticalalignment='top',
                 color='red',
                 transform=ax1.transAxes)
        plt.show()
    else:
        messagebox.showerror("错误","请先勾选需要处理的定位点！")

def protect_leval():
    protect_levals = []
    global CALL_TRUTH_GNSS_ERROR_CAL
    CALL_TRUTH_GNSS_ERROR_CAL = False

    with open('truth_data_match.txt', 'r', encoding='utf-8') as file:
        lines = file.readlines()

    if CheckVar2.get() == 1 and protect_leval_num != -1:
        for line in lines:
            parts = line.split(',')
            protect_leval = float(parts[protect_leval_num])
            protect_levals.append(protect_leval)

        result_text = '速度误差数据处理完成！'
        label_text(result_text)

        fig, ax1 = plt.subplots()
        if protect_levals:
            ax1.plot(protect_levals, label='Protect Leval')

        ax1.set_xlabel('Index')
        ax1.set_ylabel('Radius')
        ax1.set_title('Protect Leval')
        ax1.legend()
        ytext = 10
        offset = 20
        if CheckVar5.get() == 1 or CheckVar6.get() == 1:
            start_end_index = get_ins_index(flag = 2)
            for key, (start_index, end_index, recover_fixed_index, last_ponit_index, last_ponit_time, end_time, target_info) in start_end_index.items():
                last_protect_leval = protect_levals[last_ponit_index]
                ax1.annotate(f'LPT: {last_ponit_time}\nLPL: {last_protect_leval:.2f}m',
                             xy=(last_ponit_index, last_protect_leval),
                             xytext=(10, ytext),
                             textcoords='offset points',
                             arrowprops=dict(arrowstyle='->'))
                ytext = ytext + offset
        plt.show()

    else:
        messagebox.showerror("错误","请先勾选需要处理的定位点或configure.ini对应的protect_leval！")

    CALL_TRUTH_GNSS_ERROR_CAL = True

CALL_TRUTH_GNSS_ERROR_CAL = True

def guandao_map_show():
    global CALL_TRUTH_GNSS_ERROR_CAL
    truth_pos, gnss_pos, ins_pos, gnss_ins_pos , gnss_accuracys= gnss_pos_cal()
    #print(gnss_ins_pos)
    CALL_TRUTH_GNSS_ERROR_CAL = False
    get_index = get_ins_index(flag=0)
    end_indices = []

    map_center = [31.840206, 117.128922]
    initial_zoom = 18

    # 天地地图瓦片 URL
    tiandi_tiles_url = 'http://t0.tianditu.gov.cn/img_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=img&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=164da69c5eb830df43aaf4006cfe8f85'

    for key in get_index:
        start_index, end_index, recover_fixed_index, last_ponit_index, _, _, _ = get_index[key]
        end_indices.append((start_index, end_index, recover_fixed_index, last_ponit_index))

    #print(end_indices)

    start_index = 0
    for i, (start_index, end_index, recover_fixed_index, last_ponit_index) in enumerate(end_indices):
        map_obj = folium.Map(
            location=map_center,
            zoom_start=initial_zoom
        )

        measure_control = MeasureControl()
        map_obj.add_child(measure_control)

        draw = Draw(export=True)
        draw.add_to(map_obj)

        tiandi_tiles = folium.TileLayer(
            tiles=tiandi_tiles_url,
            attr='天地地图',
            name='天地地图',
        )
        tiandi_tiles.add_to(map_obj)

        # 仅作地图点的种类标记
        truth_layer = folium.FeatureGroup(name='<span style="color: red;">Truth Points</span>')
        gnss_layer = folium.FeatureGroup(name='<span style="color: blue;">GNSS Points</span>')
        ins_layer = folium.FeatureGroup(name='<span style="color: yellow;">INS Points</span>')
        gnss_ins_layer = folium.FeatureGroup(name='<span style="color: green;">GNSS_INS Points</span>')

        gnss_accuracy = gnss_accuracys[start_index:end_index]
        low_four_decimal, high_four_decimal = gnss_accuracy_hex_list(gnss_accuracy)

        # 添加 真值 位置标记
        sliced_truth_pos = truth_pos[start_index:end_index]
        if sliced_truth_pos:
            filtered_truth_pos = [point for point in sliced_truth_pos if point != [0.0, 0.0]]
            ant_path = AntPath(
                locations=filtered_truth_pos,
                color='red',
                pulse_color='white',
                dash_array=[10, 20],
                delay=800
            )
            ant_path.add_to(truth_layer)

        # 添加 GNSS 位置标记
        sliced_gnss_pos = gnss_pos[start_index:end_index]
        if sliced_gnss_pos:
            segments_gnss = []
            current_segment_gnss = []
            for point, low in zip(sliced_gnss_pos, low_four_decimal):
                if low != 0 and point != [0.0, 0.0]:
                    current_segment_gnss.append(point)
                else:
                    # 如果 low 为 0 或者点为 [0.0, 0.0]，结束当前段并添加到 segments 列表
                    if current_segment_gnss:
                        segments_gnss.append(current_segment_gnss)
                        current_segment_gnss = []

            if current_segment_gnss:
                segments_gnss.append(current_segment_gnss)

            for segment_gnss in segments_gnss:
                if segment_gnss:
                    ant_path = AntPath(
                        locations=segment_gnss,
                        color='blue',
                        pulse_color='white',
                        dash_array=[10, 20],
                        delay=800
                    )
                    ant_path.add_to(gnss_layer)
            folium.Marker(
                location=gnss_pos[last_ponit_index],
                popup=f'Last Point: {gnss_pos[last_ponit_index]}',
                color='red',
            ).add_to(gnss_layer)

        # 添加 惯导 位置标记
        sliced_ins_pos = ins_pos[start_index:end_index]
        icon = folium.Icon(color='red')
        if sliced_ins_pos:
            filtered_ins_pos = [point for point in sliced_ins_pos if point != [0.0, 0.0]]
            ant_path = AntPath(
                locations=filtered_ins_pos,
                color='yellow',
                pulse_color='white',
                dash_array=[10, 20],
                delay=800
            )
            ant_path.add_to(ins_layer)
            folium.Marker(
                location=ins_pos[recover_fixed_index],
                popup=f'recover fixed: {ins_pos[recover_fixed_index]}',
                color='red',
            ).add_to(ins_layer)

        # 添加 融合点 位置标记
        sliced_gnss_ins_pos = gnss_ins_pos[start_index:end_index]
        if sliced_gnss_ins_pos:
            segments_gnss_ins = []
            current_segment_gnss_ins = []
            for point, low in zip(sliced_gnss_ins_pos, low_four_decimal):
                #print(f"gnss_ins point: {point}, low: {low}")
                if low != 0 and point != [0.0, 0.0]:
                    current_segment_gnss_ins.append(point)
                else:
                    # 如果 low 为 0 或者点为 [0.0, 0.0]，结束当前段并添加到 segments 列表
                    if current_segment_gnss_ins:
                        segments_gnss_ins.append(current_segment_gnss_ins)
                        current_segment_gnss_ins = []

            if current_segment_gnss_ins:
                segments_gnss_ins.append(current_segment_gnss_ins)

            for segment_gnss_ins in segments_gnss_ins:
                #print(f'\n{segment_gnss_ins}')
                if segment_gnss_ins:
                    ant_path = AntPath(
                        locations=segment_gnss_ins,
                        color='green',
                        pulse_color='white',
                        dash_array=[10, 20],
                        delay=800
                    )
                    ant_path.add_to(gnss_ins_layer)

        truth_layer.add_to(map_obj)
        gnss_layer.add_to(map_obj)
        ins_layer.add_to(map_obj)
        gnss_ins_layer.add_to(map_obj)

        folium.LayerControl().add_to(map_obj)

        map_file = f'map_{i}.html'
        map_obj.save(map_file)
        # 打开地图
        webbrowser.open('file://' + os.path.realpath(map_file))
        start_index = end_index

    CALL_TRUTH_GNSS_ERROR_CAL = True


def scene_layout_catch():
    start_end_index = []
    elevated_data = []
    boulevard_data  = []
    high_speed_data  = []
    opensky_data  = []
    skyscraper_data  = []
    tunnel_data  = []

    #长江西路高架、南北一号高架、林荫道、高速、乡间小道
    ruller_pos = [(117.176925, 31.853407), (117.246839, 31.855375), (117.294457, 31.850000), (117.296482, 31.819580),
                  (117.262335, 31.860903), (117.268570, 31.851207),
                  (117.298463, 31.801870), (117.135157, 31.855772),
                  (117.114420, 31.861050), (117.109403, 31.860678)]
    try:
        with open('truth_data_match.txt', 'r', encoding='utf-8') as file:
            lines = file.readlines()
    except Exception as e:
        messagebox.showerror("错误", f"truth_data_match.txt文件错误：{str(e)}")

    for point in ruller_pos:
        center_lat = point[1]
        center_lon = point[0]
        for index, line in enumerate(lines):
            parts = line.split(',')
            current_lon = float(parts[truth_lon_num])
            current_lat = float(parts[truth_lat_num])
            dist = geodesic((current_lat, current_lon), (center_lat, center_lon)).kilometers * 1000
            if dist <= 20:
                catch_index = index
                start_end_index.append(catch_index)
                found = True
                break
        if not found:  # 如果没有找到符合条件的点
            start_end_index.append(0)

    print("start_end_index: ", start_end_index)
    start_end_group = []
    for i in range(0, len(start_end_index), 2):
        if i + 1 < len(start_end_index):
            group = (start_end_index[i], start_end_index[i + 1])
            start_end_group.append(group)

    if start_end_group[0][0] !=0 and start_end_group[0][1] !=0 and start_end_group[0][0] < start_end_group[0][1]:
        elevated_data1 = lines[start_end_group[0][0]: start_end_group[0][1]]
    if start_end_group[1][0] != 0 and start_end_group[1][1] != 0 and start_end_group[1][0] < start_end_group[1][1]:
        elevated_data2 = lines[start_end_group[1][0]: start_end_group[1][1]]
        parts = elevated_data2[0].split(',')
        parts[gnss_lon_num] = '0'
        parts[gnss_lat_num] = '0'
        parts[truth_lon_num] = '0'
        parts[truth_lat_num] = '0'
        elevated_data2[0] = ','.join(parts)
    with open('overpass.txt', "w", encoding='utf-8') as file:
        file.writelines(elevated_data1)
        file.writelines(elevated_data2)

    if start_end_group[2][0] != 0 and start_end_group[2][1] != 0 and start_end_group[2][0] < start_end_group[2][1]:
        avenue_data = lines[start_end_group[2][0]: start_end_group[2][1]]
    with open('avenue.txt', "w", encoding='utf-8') as file:
        file.writelines(avenue_data)

    if start_end_group[3][0] != 0 and start_end_group[3][1] != 0 and start_end_group[3][0] < start_end_group[3][1]:
        high_speed_data = lines[start_end_group[3][0]: start_end_group[3][1]]
    with open('high_speed.txt', "w", encoding='utf-8') as file:
        file.writelines(high_speed_data)

    if start_end_group[4][0] != 0 and start_end_group[4][1] != 0 and start_end_group[4][0] < start_end_group[4][1]:
        trail_data = lines[start_end_group[4][0]: start_end_group[4][1]]
    with open('trail.txt', "w", encoding='utf-8') as file:
        file.writelines(trail_data)


#button_value = 0
#param：控制Radiobutton双击取消勾选

def on_radio_select():
    """
    :return: 返回当前勾选Radiobutton的值，用于判断处理不同场景下的文件
    """
    current_value = scene_var.get()
    #print(current_value)
    return current_value

def clear_radio():
    """
    清除所有场景的按钮值
    :return: 无
    """
    scene_var.set(0)

def truth_sys():
    """
    真值和惯导工具界面排布
    """
    delete_widget()

    file_choice = tk.LabelFrame(window, height = 260, width = 450, text="文件选择", relief = 'groove')
    file_choice.place(relx = 0, rely = 0)
    pos_choice = tk.LabelFrame(window, height = 70, width = 450, text="定位点选择", relief = 'groove')
    pos_choice.place(relx = 0.0, rely = 0.45)
    heading_data = tk.LabelFrame(window, height = 70, width = 450, text="角度处理", relief = 'groove')
    heading_data.place(relx = 0.0, rely = 0.55)
    result_show_data = tk.LabelFrame(window, height = 200, width = 450, text="处理结果", relief = 'groove')
    result_show_data.place(relx = 0.5, rely = 0)
    pos_data = tk.LabelFrame(window, height = 240, width = 450, text="经纬度处理", relief = 'groove')
    pos_data.place(relx = 0.0, rely = 0.65)
    speed_choice = tk.LabelFrame(window, height = 70, width = 450, text="速度处理", relief = 'groove')
    speed_choice.place(relx = 0.5, rely = 0.45)
    altitude_choice = tk.LabelFrame(window, height = 70, width = 450, text="海拔高度处理", relief = 'groove')
    altitude_choice.place(relx = 0.5, rely = 0.55)
    guandao_choice = tk.LabelFrame(window, height = 240, width = 300, text="惯导特殊数据处理", relief = 'groove')
    guandao_choice.place(relx = 0.5, rely = 0.65)
    last_point_choice = tk.LabelFrame(window, height=240, width=140, text="ADR-3 Time", relief='groove')
    last_point_choice.place(relx=0.81, rely=0.65)
    create_tooltip(last_point_choice,"功能：GNSS失锁前一个定位成功点的时间！")
    scene_layout = tk.LabelFrame(window, height=110, width=450, text="场景", relief='groove')
    scene_layout.place(relx=0.5, rely=0.29)

    truth_but = tk.Button(file_choice, text = '真值文件', pady = 1, padx = 1, command = load_truth, width = 10)
    truth_but.place(relx = 0.4, rely = 0.14)
    create_tooltip(truth_but, "功能：选取真值系统的数据，txt格式")
    messages_but = tk.Button(file_choice, text = '报文文件', pady = 1, padx = 1, command = load_messages, width = 10)
    messages_but.place(relx = 0.4, rely = 0.48)
    create_tooltip(messages_but, "功能：选取需要对比设备的报文文件")
    rmc_mess_but = tk.Button(file_choice, text='NMEA文件', pady=1, padx=1, command=load_nmeas, width=10)
    rmc_mess_but.place(relx=0.4, rely=0.83)
    create_tooltip(messages_but, "功能：选取需要对比设备的报文文件")
    global truth_Entry
    truth_Entry = tk.Entry(file_choice, width = 62)
    truth_Entry.place(relx = 0.01, rely = 0.01)
    global messages_Entry
    messages_Entry = tk.Entry(file_choice, width = 62)
    messages_Entry.place(relx = 0.01, rely = 0.32)
    global nmea_Entry
    nmea_Entry = tk.Entry(file_choice, width=62)
    nmea_Entry.place(relx=0.01, rely=0.68)
    global result_label
    result_label = tk.Label(result_show_data, text="",justify='left', wraplength=410, width=40, height=8,font=("Arial", 12, "bold"), fg="blue",anchor="w")
    result_label.place(relx = 0.01, rely = 0.01)

    global start_time_entry, end_time_entry
    head_deal_but = tk.Button(window, text = '数据处理', pady = 1, padx = 1, command = heading_deal, width = 12, height = 2, bg ="blue")
    head_deal_but.place(relx = 0.30, rely = 0.38)
    label1 = tk.Label(window, text='start time:', justify='center', width=8, height=1, )
    label1.place(relx=0.08, rely=0.38)
    label2 = tk.Label(window, text='end time:', justify='center', width=8, height=1, )
    label2.place(relx=0.08, rely=0.42)
    start_time_entry = tk.Entry(window, width=14)
    start_time_entry.place(relx=0.15, rely=0.38)
    end_time_entry = tk.Entry(window, width=14)
    end_time_entry.place(relx=0.15, rely=0.42)


    head_show_but = tk.Button(heading_data, text = 'Heading显示', pady = 1, padx = 1, command = tru_heading_show, width = 12)
    head_show_but.place(relx = 0.01, rely = 0.1)
    create_tooltip(head_show_but, "功能：显示不同定位点的航向角")
    head_error_but = tk.Button(heading_data, text = '误差处理', pady = 1, padx = 1, command = heading_error_show, width = 12)
    head_error_but.place(relx = 0.35, rely = 0.1)
    create_tooltip(head_error_but, "功能：显示其它定位点与真值航向角的角度差")
    head_error_show_but = tk.Button(heading_data, text ='离散分布', pady = 1, padx = 1, command = heading_dispersion, width = 12)
    head_error_show_but.place(relx = 0.72, rely = 0.1)
    create_tooltip(head_error_show_but, "功能：将角度差数据按5°间隔作概率分布")

    pos_deal_but = tk.Button(pos_data, text = '地图显示', pady = 1, padx = 1, command = map_truth_show, width = 14, height = 1)
    pos_deal_but.place(relx = 0.35, rely = 0.05)
    create_tooltip(pos_deal_but, "功能：定位点显示在地图上，点越多加载地图时间越长，特别是第一次加载地图，最好不要超过5000个点")

    global CheckVar1
    global CheckVar2
    global CheckVar3
    global CheckVar4
    global CheckVar5
    global CheckVar6
    global CheckVar7


    CheckVar1 = IntVar()
    CheckVar2 = IntVar()
    CheckVar3 = IntVar()
    CheckVar4 = IntVar()
    CheckVar5 = IntVar()
    CheckVar6 = IntVar()
    CheckVar7 = IntVar()


    check1 = tk.Checkbutton(pos_choice,text = "真值", variable = CheckVar1, onvalue=1, offvalue=0)
    check1.place(relx = 0.01, rely = 0.05)
    create_tooltip(check1, "功能：北云真值数据，转换成txt格式！")
    check2 = tk.Checkbutton(pos_choice,text = "GNSS", variable = CheckVar2, onvalue=1, offvalue=0)
    check2.place(relx = 0.25, rely = 0.05)
    create_tooltip(check2, "功能：设备GNSS定位经纬度！")
    check3 = tk.Checkbutton(pos_choice,text = "INS", variable = CheckVar3, onvalue=1, offvalue=0)
    check3.place(relx = 0.5, rely = 0.05)
    create_tooltip(check3, "功能：设备纯惯导经纬度！")
    check4 = tk.Checkbutton(pos_choice,text = "GNSS_INS", variable = CheckVar4, onvalue=1, offvalue=0)
    check4.place(relx = 0.75, rely = 0.05)
    create_tooltip(check4, "功能：设备融合点经纬度！")

    check5 = tk.Checkbutton(guandao_choice, text="隧道", variable=CheckVar5, onvalue=1, offvalue=0)
    check5.place(relx=0.5, rely=0.05)
    create_tooltip(check5, "隧道：适合正常路径场测，通过真值的pos type为非NONE判断失锁结束")
    check6 = tk.Checkbutton(guandao_choice, text="模拟", variable=CheckVar6, onvalue=1, offvalue=0)
    check6.place(relx=0.7, rely=0.05)
    create_tooltip(check6, "模拟：适合opensky下场测，通过GTFRI报文的GNSS accuracy的低4位非0判断失锁结束")
    check7 = tk.Checkbutton(window, text="惯导", variable=CheckVar7, onvalue=1, offvalue=0)
    check7.place(relx=0.02, rely=0.40)
    create_tooltip(check7, "功能：如果处理的是惯导数据，请勾选！区别在于：处理数据匹配时勾选的话使用的是UTC和惯导时间匹配，否则用UTC和Sendtime匹配")


    gnss_truth_deal_but = tk.Button(pos_data, text = 'GNSS误差', pady = 1, padx = 1, command = lambda: truth_gnss_error(flag = 1), width = 12)
    gnss_truth_deal_but.place(relx = 0.01, rely = 0.25)
    create_tooltip(gnss_truth_deal_but, "功能：计算GNSS经纬度与真值的距离差")
    gnss_truth_deal_but = tk.Button(pos_data, text = 'GNSS误差分布', pady = 1, padx = 1, command = lambda: gnss_error_discrete(flag = 1), width = 12)
    gnss_truth_deal_but.place(relx = 0.01, rely = 0.5)
    create_tooltip(gnss_truth_deal_but, "功能：将距离差按5m作概率分布")
    gnss_truth_deal_but = tk.Button(pos_data, text = 'GNSS最大误差\n显示', pady = 1, padx = 1, command = lambda: gnss_error_max(flag = 1), width = 12, height = 2, anchor='center')
    gnss_truth_deal_but.place(relx = 0.01, rely = 0.75)
    create_tooltip(gnss_truth_deal_but, "功能：取最大误差前100个点和后100个点显示在地图上")

    ins_truth_deal_but = tk.Button(pos_data, text = 'INS误差', pady = 1, padx = 1, command = lambda: truth_gnss_error(flag = 2), width = 12, height = 1)
    ins_truth_deal_but.place(relx = 0.35, rely = 0.25)
    create_tooltip(ins_truth_deal_but, "功能：计算惯导点经纬度与真值的距离差")
    ins_truth_deal_but = tk.Button(pos_data, text = 'INS误差分布', pady = 1, padx = 1, command = lambda: gnss_error_discrete(flag = 2), width = 12)
    ins_truth_deal_but.place(relx = 0.35, rely = 0.5)
    create_tooltip(ins_truth_deal_but, "功能：将距离差按5m作概率分布")
    ins_truth_deal_but = tk.Button(pos_data, text = 'INS最大误差\n显示', pady = 1, padx = 1, command = lambda: gnss_error_max(flag = 2), width = 12, height = 2, anchor='center')
    ins_truth_deal_but.place(relx = 0.35, rely = 0.75)
    create_tooltip(ins_truth_deal_but, "功能：取最大误差前100个点和后100个点显示在地图上")

    gnss_ins_truth_deal_but = tk.Button(pos_data, text = 'GNSS_INS误差', pady = 1, padx = 1, command = lambda: truth_gnss_error(flag = 3), width = 14)
    gnss_ins_truth_deal_but.place(relx = 0.72, rely = 0.25)
    create_tooltip(gnss_ins_truth_deal_but, "功能：计算融合点经纬度与真值的距离差")
    gnss_ins_truth_deal_but = tk.Button(pos_data, text = 'GNSS_INS误差分布', pady = 1, padx = 1, command = lambda: gnss_error_discrete(flag = 3), width = 14)
    gnss_ins_truth_deal_but.place(relx = 0.72, rely = 0.5)
    create_tooltip(gnss_ins_truth_deal_but, "功能：将距离差按5m作概率分布")
    gnss_ins_truth_deal_but = tk.Button(pos_data, text = 'GNSS_INS最大误差\n显示', pady = 1, padx = 1, command = lambda: gnss_error_max(flag = 3), width = 14, height = 2, anchor='center')
    gnss_ins_truth_deal_but.place(relx = 0.72, rely = 0.75)
    create_tooltip(gnss_ins_truth_deal_but, "功能：取最大误差前100个点和后100个点显示在地图上")

    speed_deal_but = tk.Button(speed_choice, text='速度显示', pady=1, padx=1, command = speed_show, width=14)
    speed_deal_but.place(relx=0.01, rely=0.1)
    speed_deal_show_but = tk.Button(speed_choice, text='速度误差显示', pady=1, padx=1, command=speed_error_show, width=14)
    speed_deal_show_but.place(relx=0.35, rely=0.1)

    height_deal_but = tk.Button(altitude_choice, text='高度显示', pady=1, padx=1, command=height_show, width=14)
    height_deal_but.place(relx=0.01, rely=0.1)
    height_deal_show_but = tk.Button(altitude_choice, text='高度误差显示', pady=1, padx=1, command=height_error_show, width=14)
    height_deal_show_but.place(relx=0.35, rely=0.1)

    bias_gyro_deal_but = tk.Button(guandao_choice, text='Bias_gyro绘图', pady=1, padx=1, command = lambda: bias_scale(flag = 1), width=12)
    bias_gyro_deal_but.place(relx=0.01, rely=0.05)
    bias_acc_deal_but = tk.Button(guandao_choice, text='Bias_acc绘图', pady=1, padx=1, command = lambda: bias_scale(flag = 2), width=12)
    bias_acc_deal_but.place(relx=0.01, rely=0.3)
    scale_gyro_deal_but = tk.Button(guandao_choice, text='Scale_gyro绘图', pady=1, padx=1, command = lambda: bias_scale(flag = 3), width=12)
    scale_gyro_deal_but.place(relx=0.01, rely=0.55)
    scale_acc_deal_but = tk.Button(guandao_choice, text='Scale_acc绘图', pady=1, padx=1, command = lambda: bias_scale(flag = 4),width=12)
    scale_acc_deal_but.place(relx=0.01, rely=0.8)

    protect_leval_but = tk.Button(guandao_choice, text='Pro_Leval绘图', pady=1, padx=1, command= protect_leval, width=12)
    protect_leval_but.place(relx=0.55, rely=0.3)

    guandao_map_but = tk.Button(guandao_choice, text='惯导Map分段', pady=1, padx=1, command= guandao_map_show, width=12)
    guandao_map_but.place(relx=0.55, rely=0.55)
    create_tooltip(guandao_map_but, "功能：自动根据Last Point来将每次测试GNSS失锁分开显示，裁切时间点：初始化开始到GNSS失锁结束！确保最后一次失锁结束有定位成功的报文")

    global initial_time_entry
    initial_time_entry = tk.Entry(last_point_choice, width = 16)
    initial_time_entry.place(relx=0.06, rely=0.05)

    #场景布局
    scene_data_but = tk.Button(scene_layout, text='场景数据处理', pady=1, padx=1, bg = 'blue', command=scene_layout_catch, width=12, height = 2)
    scene_data_but.place(relx=0.05, rely=0.20)
    scene_clear_but = tk.Button(scene_layout, text='清除', pady=1, padx=1, bg='red', command=clear_radio, width=8, height=1)
    scene_clear_but.place(relx=0.85, rely=0.65)

    global scene_var
    scene_var = tk.IntVar()
    scene_var.set(0)

    # 创建 6 个 Radiobutton
    elevated_checkbut = tk.Radiobutton(scene_layout, text="高架下", variable=scene_var, value=1, command = on_radio_select)
    elevated_checkbut.place(relx=0.35, rely=0.00)
    boulevard_checkbut = tk.Radiobutton(scene_layout, text="林荫道", variable=scene_var, value=2, command = on_radio_select)
    boulevard_checkbut.place(relx=0.35, rely=0.30)
    high_speed_checkbut = tk.Radiobutton(scene_layout, text="高速", variable=scene_var, value=3, command = on_radio_select)
    high_speed_checkbut.place(relx=0.35, rely=0.60)
    opensky_checkbut = tk.Radiobutton(scene_layout, text="空旷路", variable=scene_var, value=4, command = on_radio_select)
    opensky_checkbut.place(relx=0.55, rely=0.00)
    skyscraper_checkbut = tk.Radiobutton(scene_layout, text="高楼", variable=scene_var, value=5, command = on_radio_select)
    skyscraper_checkbut.place(relx=0.55, rely=0.30)
    tunnel_checkbut = tk.Radiobutton(scene_layout, text="隧道", variable=scene_var, value=6, command = on_radio_select)
    tunnel_checkbut.place(relx=0.55, rely=0.60)
    trail_checkbut = tk.Radiobutton(scene_layout, text="乡间小路", variable=scene_var, value=7, command=on_radio_select)
    trail_checkbut.place(relx=0.75, rely=0.00)

def truth_multi_sys():
    delete_widget()
    file_choice = tk.LabelFrame(window, height=690, width=450, text="文件选择", relief='groove')
    file_choice.place(relx=0.01, rely=0)
#main_menu.add_command (label = "真值系统", command = Truth_sys)

if platform.system() == 'Darwin':
    # Apple 菜单
    apple_menu = tk.Menu(window, name='apple')
    apple_menu.add_command(label='关于 Tool', command=lambda: messagebox.showinfo('关于', '版本 1.0'))
    apple_menu.add_separator()
    apple_menu.add_command(label='退出 Tool', command=window.quit)

    # 文件菜单
    file_menu = tk.Menu(main_menu, tearoff=False)

    func_menu = tk.Menu(main_menu, tearoff=False)
    main_menu.add_cascade(label='功能', menu=func_menu)

    # 添加旋转矩阵转换菜单项到 main_menu

    func_menu.add_command(label='旋转矩阵转换', command=rotation_euler)
    func_menu.add_command(label="欧拉角转换", command=euler_rotation)
    func_menu.add_command(label="距离计算", command=distance_show)
    func_menu.add_command(label="小工具集合", command=small_tool_collection)
    func_menu.add_command(label="计时器", command=Timer)
    func_menu.add_command(label="绘图", command=menu_show)
    func_menu.add_command(label="惯导功能", command=guandao)
    func_menu.add_command(label="中间件", command=protocol_analyze)
    func_menu.add_command(label="显示地图", command=map_show)
    func_menu.add_command(label="异常监控", command=monitor_func)
    func_menu.add_command(label="真值系统", command=truth_sys)
    func_menu.add_command(label="真值多文件处理", command=truth_multi_sys)


    # 配置窗口菜单为 main_menu
    window.configure(menu=main_menu)
else:
    # 其他系统直接使用 main_menu
    main_menu.add_command(label="旋转矩阵转换", command=rotation_euler)
    main_menu.add_command(label="欧拉角转换", command=euler_rotation)
    main_menu.add_command(label="距离计算", command=distance_show)
    main_menu.add_command(label="小工具集合", command=small_tool_collection)
    main_menu.add_command(label="计时器", command=Timer)
    main_menu.add_command(label="绘图", command=menu_show)
    main_menu.add_command(label="惯导功能", command=guandao)
    main_menu.add_command(label="中间件", command=protocol_analyze)
    main_menu.add_command(label="显示地图", command=map_show)
    main_menu.add_command(label="异常监控", command=monitor_func)
    main_menu.add_command(label="真值系统", command=truth_sys)
    main_menu.add_command(label="真值多文件处理", command=truth_multi_sys)
    window.configure(menu=main_menu)

window.config (menu=main_menu)
window.mainloop()
